Introduction

Linear programming is one of the most extensively used techniques in the toolbox of quantitative methods of optimization. Its origins date as early as 1937, when Leonid Kantorovich published his paper A new method of solving some classes of extremal problems. Kantorovich developed linear programming as a technique for planning expenditures and returns in order to optimize costs to the army and increase losses to the enemy.

The method was kept secret until 1947, when George B. Dantzig published the simplex method for solving linear programming

Roughly speaking, the linear programming problem consists in optimizing (that is, either minimize or maximize) the value of a linear objective function of a vector of decision variables, considering that the variables can only take the values defined by a set of linear constraints. Linear programming is a case of mathematical programming, where objective function and constraints are linear.

A formulation of a linear program in its canonical form of maximum is:

\[max Z = \sum_{i = 1} ^ n c_i x_i\] Subject to

\[\sum_{i = i} ^ n \sum_{j = i} ^ m a_{ij}x_i \le b_j\] \[\sum_{i = i} ^ n \sum_{j = i} ^ m a_{ij}x_i \ge b_k\]

\[\sum_{i = i} ^ n \sum_{j = i} ^ m a_{ij}x_i = b_l\]

The model has the following elements:

  1. An objective function of the n decision variables \(x_i\) . Decision variables are affected by the cost coefficients \(c_j\)

  2. A set of m constraints, in which a linear combination of the variables affected by coefficients \(a_{ij}\) has to be less or equal than its right hand side value \(b_i\) (constraints with signs greater or equal or equalities are also possible)

  3. The bounds of the decision variables. In this case, all decision variables have to be nonnegative

The LP formulation shown above can be expressed in matrix form as follows:

\[max Z = \mathbf{c}^T \mathbf{x}\]

Subject to

\[\mathbf{Ax = b}\]

\[\mathbf{x} \ge 0\]

Modeling Linear Programs

Let’s consider the following situation:

A small business sells two products, named Product 1 and Product 2. Each tonne of Product 1 consumes 30 working hours, and each tonne of Product 2 consumes 20 working hours. The business has a maximum of 2700 working hours for the period considered. As for machine hours, each tonne of Products 1 and 2 consumes 5 and 10 machine hours, respectively. There are 850 machine hours available.

Each tonne of Product 1 yields 20 Me of profit, while Product 2 yields 60 Me for each tonne sold. For technical reasons, the firm must produce a minimum of 95 tonnes in total between both products. We need to know how many tonnes of Product 1 and 2 must be produced to maximize total profit.

This situation is apt to be modeled as a PL model. First, we need to define the decision variables. In this case we have:

  1. \(x_1\) number of tonnes produced and sold of Product 1
  2. \(x_2\) number of tonnes produced and sold of Product 2

The cost coefficients of these variables are 20 and 60, respectively. Therefore, the objective function is defined multiplying each variable by its corresponding cost coefficient

\[maxZ = 20 x_1 + 60 x_2\]

A constraint WH making that the total amount of working hours used in Product 1 and Product 2, which equals \(30 x_1 + 20 x_2\), is less or equal than 2,700 hours.

A similar constraint MH making that the total machine hours \(5 x_1 + 10 x_2\) are less or equal than 850.

A constraint making that the total units produced and sold \(x_1 + x_2\) are greater or equal than 95

Putting all this together, and considering that the decision variables are nonnegative, the LP that maximizes profit is:

\[maxZ = 20 x_1 + 60 x_2\]

Subject to

\[30 x_1 + 20 x_2 \le 2700\]

\[5 x_1 + 10 x_2 \le 850\] \[x_1 + x_2 \le 95 \] \[x_1, x_2 \ge 0\]

The following code solves that LP with two variables:

prod.sol$objval
[1] 4900

A Trasportation Problem

Let’s consider a transportation problem of two origins a and b, and three destinations 1, 2 and 3. In the Table below are presented the cost \(c_{ij}\) of transporting one unit from the origin \(i\) to destination \(j\), and the maximal capacity of the origins and the required demand in the destinations.

We need to know how we must cover the demand of the destinations at a minimal cost.


Table2 = data.frame(Parameter = c('Origin A', 'Origin B', 'Demand'),
                    Dest.1 = c(8, 2, 40),
                    Dest.2 = c(6, 4, 35),
                    Dest.3 = c(3, 9, 25),
                    Capacity = c(70, 40, NA))
Table2
NA

This situation can be modeled with a LP with the following elements:

  1. Decision variables of the form $x_{ij} , representing units transportedfrom origin \(i\) to destination \(j\)
  2. An objective function with cost coefficients equal to \(c_{ij}\)
  3. Two sets of constraints: a less or equal set of constraints for each origin, limiting the units to be transported, and a greater of equal set of constraints representing that the demand of each destination must be covered.

The resulting LP is:

\[maxZ = 8x_{a1} + 6x_{a2} + 3x_{a3} + 2x_{b1} + 4x_{b2} + 9x_{b3}\]

Subject to:

\[x_{a1} + x_{a2} + x_{a3} \le 70\] \[x_{b1} + x_{b2} + x_{b3} \le 40\] \[x_{a1} + x_{b1} \ge 40\] \[x_{a2} + x_{b2} \ge 35\] \[x_{a3} + x_{b3} \ge 25\]

\[x_{ij} \ge 0\]

sol
     [,1] [,2] [,3]
[1,]    0   35   25
[2,]   40    0    0

Transportation by Truks

In Table3 can be found the quarterly demand (in tonnes) and the acquisition costs per tonne (in ke per tonne) for each quarter of raw materials for a chemical plant. All purchases in a given quarter can be used to cover the demand of the present quarter, or the demand of quarters in the future. The costs of stocking are of 8k USD per tonne stored at the end of each quarter. The stocks at the beginning of first quarter are of 100 tonnes, and it is needed the same amount of stock at the end of the fourth quarter.

In addition to the purchase and storage costs, the transportation costs have to be considered. All the purchased quantity of raw materials has to be transported, using any combination of the two available truck models:

  1. Small trucks: cost of 700k uSD, and capacity of 500 tonnes.
  2. Large trucks: cost of 1,400k USD, and capacity of 1,200 tonnes.

We need to define a linear programming model that allows the minimization of the total costs: acquisition, storage and transport, obtaining the amount raw materials to purchase, and the amount of trucks of both kinds to be contracted each quarter.

Table3 = data.frame(Quarter = c('Demand', 'Unit Cost'),
                    T1 = c(1000, 20) ,
                    T2 = c(1200, 25),
                    T3 = c(1500, 30),
                    T4 = c(1800, 40))

Table3

The variables to define are:

  1. \(q_i\) continuous: tonnes of raw material to purchase in quarter i
  2. \(s_i\) continuous: tonnes in stock at the end of quarter i, and \(s_0\) as the initial stock
  3. \(t_i\) integer: small trucks to contract in quarter i
  4. \(u_i\) integer: large trucks to contract in quarter i

Once defined the variables, two sets of constraints have to be defined:

  1. Constraints assuring that the purchase plan meets the demand commitments. These are of the form \(s_{i-1} + q_i - s_i = d_i\), being \(d_i\) the demand of the quarter.
  2. Constraints assuring that a sufficient number of each kind of trucks is contracted: \(q_i \le 500t_i + 12004 u_i\)

The resulting model is:

\[minZ = \sum_{i = 1} ^ 4 (c_iq_i + 8s_i + 700t_i + 1400u_i) \] Subject to:

\[s_{i-1} + q_i - s_i = d_i \Rightarrow\forall i = 1,...,4\] \[q_i - 500t_i - 1200ui \le 0 \Rightarrow\forall i = 1,...,4\]

\[s_0 = s_4 = 100\]

\[s_i, q_i \ge 0\]

prod.trans$solution
 [1]  900.000000 1200.000000 3400.000000    0.000000  100.000000    0.000000    0.000000 1900.000000  100.000000    0.000000    0.000000    0.000000
[13]    0.000000    0.750000    1.000000    2.833333    0.000000

In this case, the solution has some practical and technical issues. We supposed to have only integer solutions and in the case of \(u_1 = 0.75\) and \(u_3 = 2.83\) this condition is not true.

To fix this, we use Integer Programming models ensuring all variables to be integer:

Hiring & Firing

In Table4 are listed the needs of pilots able to flight an A320 for the following six months. The cost of a pilot’s salary is $8k USD per month. At the beginning of Month 1 the airline has a staff of 20 pilots, but this staff can be adjusted each month.

Pilots can be hired and fired at the beginning of each month. Newly hired pilots can start working at the same month, and fired pilots stop working the same day they are fired. The cost of firing a pilot is $10k USD, and the hiring cost is of 5K USD per pilot. If it is convenient, the airline can have a staff of pilots larger than the actual needs.

  1. Define a linear programming model to obtain the pilots to hire and fire each month to minimize the total cost of pilot staff (costs of salary plus hiring and firing costs).

To model this situation, we’ll have to define the following variables:

  1. Variables \(h_i\): pilots hired at the beginning of month i
  2. Variables \(f_i\): pilots fired at the beginning of month i
  3. Variables \(s_i\): staff of pilots during month i

The model should have the following groups of constraints:

  1. Constraints assuring that the staff of pilots at the beginning of month i is equal to \(s_i = h_i - f_i + s_{i-1}\). In this case, we have that \(s_0 = 20\).
  2. Constraints assuring that variables \(s_i\) are bigger of equal to the values of staff required di listed in Table4.
table4 = data.frame(Months = c('m1', 'm2', 'm3', 'm4', 'm5', 'm6'),
                    PilotsNeed = c(30, 60, 55, 40, 45, 50))
table4

The linear problem to be solced is:

\[minZ = 5\sum_{i = 1} ^ 6 h_i + 10\sum_{i = 1} ^ 6 f_i + 8\sum_{i = 1} ^ 6 s_i\]

Subject to

\[s_i = h_i - f_i + s_{i-1} \Rightarrow\forall i = 1,...,6\] \[s_i \ge d_i \Rightarrow\forall i = 1,...,6\] \[h_i, f_i, s_i \ge 0\]

#Variable names
var.names = c('h1', 'h2', 'h3', 'h4', 'h5', 'h6',
                  'f1', 'f2', 'f3', 'f4', 'f5', 'f6',
                  's0', 's1', 's2', 's3', 's4', 's5', 's6')


#destinations run j in 1:n
obj.fun <- c(rep(5, 6), rep(10, 6), 0, rep(8, 6))
names(obj.fun) = c('h1', 'h2', 'h3', 'h4', 'h5', 'h6',
                  'f1', 'f2', 'f3', 'f4', 'f5', 'f6',
                  's0', 's1', 's2', 's3', 's4', 's5', 's6')

m <- length(obj.fun) #number of vars
n <- 7 #number of months + 1

constr1 <- matrix (0, nrow = n, ncol = m)
colnames(constr1) = var.names

#Create model for Constraint 1: s[i-1] - s[i] - f[i] + h[i] = 0

constr1[1 : 6, 1 : 6] = diag(x = 1, ncol = 6, nrow = 6)
constr1[1 : 6, 7 : 12] = diag(x = -1, ncol = 6, nrow = 6)
constr1[1 : 6, 13 : 18]  = diag(x = 1, ncol = 6, nrow = 6)
i= 1
j = 14
for(i in 1 : (n - 1)){
    constr1[i, j] = -1
    j = j + 1
}
constr1[7, 13]  = 1

#Create second constraint on minimun requirements s[i] > = d[i]
constr2 <- matrix (0, nrow = n - 1, ncol = m)
constr2[1 : 6, 14 : 19] = diag(x = 1, ncol = 6, nrow = 6)

#Bind Constr1 and Consr2 in a single matrix
constr = rbind(constr1, constr2)

#Create RHS consraintin a vector of 1 row x 13 rows
rhs = c(rep(0, 6), 20, 30, 60, 55, 40, 45, 50)

#Create direction
constr.dir <- c(rep("=", 7), rep(">=", 6))

#Solve Problem
staffing = lp('min', obj.fun, constr, constr.dir, rhs)

#Solution
staffing = staffing$solution
staffing = data.frame(Var.Name = var.names, Solution = staffing)
staffing
NA
  1. Modify the linear model to include the constraint that the airline cannot fire pilots if it has hired pilots the previous month.

Looking at the solution of the previous problem, it can be seen that this new constraint does not hold for months 2 and 3: in month 2 are hired 30 pilots, and in month 3 are fired 5 pilots. Then a new model has to be defined to account for this new restriction. To do so, we have to add a new binari variable:

  • Variabe \(b_i\): equals one if pilots are hired in month i, and zero otherwise

Then, two new sets of constraints must be added: one set assuring that \(b_i = 0 \Rightarrow f_i = 0\), and another set making that \(b_i = 1 \Rightarrow f_{i+1} = 0\)

The linear problem to be solced is:

\[minZ = 5\sum_{i = 1} ^ 6 h_i + 10\sum_{i = 1} ^ 6 f_i + 8\sum_{i = 1} ^ 6 s_i\]

Subject to

\[s_i = h_i - f_i + s_{i-1} \Rightarrow\forall i = 1,...,6\] \[s_i \ge d_i \Rightarrow\forall i = 1,...,6\] \[f_i \le Mb_i \Rightarrow \forall i = 1,...,5\] \[h_{i+1} \le M(1 - b_i) \Rightarrow \forall i = 1,...,5\]

\[b_i \in [0, 1]\] \[h_i, f_i, s_i \ge 0\]

The letter \(M\) represents an artifitial penalty and is usually asumed to be a big number. In the context of the problem, \(M=1000\) is big.

#Variable names

M = 1000 #Penalty Variable

var.names = c('h1', 'h2', 'h3', 'h4', 'h5', 'h6',
                  'f1', 'f2', 'f3', 'f4', 'f5', 'f6',
                  's0', 's1', 's2', 's3', 's4', 's5', 's6',
              'b1', 'b2', 'b3', 'b4', 'b5')


#destinations run j in 1:n
obj.fun <- c(rep(5, 6), rep(10, 6), 0, rep(8, 6), rep(0, 5))
names(obj.fun) = var.names

m <- length(obj.fun) #number of vars
n <- 7 #number of months + 1

#Create model for Constraint 1: s[i-1] - s[i] - f[i] + h[i] = 0
constr1 <- matrix (0, nrow = n, ncol = m)
colnames(constr1) = var.names


constr1[1 : 6, 1 : 6] = diag(x = 1, ncol = 6, nrow = 6)
constr1[1 : 6, 7 : 12] = diag(x = -1, ncol = 6, nrow = 6)
constr1[1 : 6, 13 : 18]  = diag(x = 1, ncol = 6, nrow = 6)
i= 1
j = 14
for(i in 1 : (n - 1)){
    constr1[i, j] = -1
    j = j + 1
}
constr1[7, 13]  = 1

#Create second constraint on minimun requirements s[i] > = d[i]
constr2 <- matrix (0, nrow = n - 1, ncol = m)
constr2[1 : 6, 14 : 19] = diag(x = 1, ncol = 6, nrow = 6)

#Create the 3rd set of constraints f[i] - M b[i] <= 0
constr3 = matrix(0, nrow = 5, ncol = m)
colnames(constr3) = var.names

constr3[, 7 : 11] = diag(1, nrow = 5, ncol = 5)
constr3[, 20 : 24] = diag(-M, nrow = 5, ncol = 5)

#Create the 4th set of constraints h[i+1] + M b[i] <= M
constr4 = matrix(0, nrow = 5, ncol = m)
colnames(constr4) = var.names

constr4[, 2 : 6] = diag(1, nrow = 5, ncol = 5)
constr4[, 20 : 24] = diag(M, nrow = 5, ncol = 5)

#Bind Constr1 and Consr2 in a single matrix
constr = rbind(constr1, constr2, constr3, constr4)

#Create RHS of ecuations
#Create RHS consraintin a vector of 1 row x 13 rows
rhs = c(rep(0, 6), 20, 30, 60, 55, 40, 45, 50, rep(0, 5), rep(M, 5))

#Create direction
constr.dir <- c(rep("=", 7), rep(">=", 6), rep('<=', 5), rep('<=', 5))

#Solve Problem
staffing = lp('min', obj.fun, constr, constr.dir, rhs, binary.vec = c(20 : 24))

#Solution
staffing = staffing$solution
staffing = data.frame(Var.Name = var.names, Solution = staffing)
staffing
NA
NA
NA
LS0tDQp0aXRsZTogIkludHJvZHVjdGlvbiB0byBMaW5lYXIgT3B0aW1pemF0aW9uIGluIFIiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIyBJbnRyb2R1Y3Rpb24NCg0KKipMaW5lYXIgcHJvZ3JhbW1pbmcqKiBpcyBvbmUgb2YgdGhlIG1vc3QgZXh0ZW5zaXZlbHkgdXNlZCB0ZWNobmlxdWVzIGluIHRoZSB0b29sYm94IG9mIHF1YW50aXRhdGl2ZSBtZXRob2RzIG9mIG9wdGltaXphdGlvbi4gSXRzIG9yaWdpbnMgZGF0ZSBhcyBlYXJseSBhcyAxOTM3LCB3aGVuICoqTGVvbmlkIEthbnRvcm92aWNoKiogcHVibGlzaGVkIGhpcyBwYXBlciAqQSBuZXcNCm1ldGhvZCBvZiBzb2x2aW5nIHNvbWUgY2xhc3NlcyBvZiBleHRyZW1hbCBwcm9ibGVtcyouIEthbnRvcm92aWNoIGRldmVsb3BlZCBsaW5lYXIgcHJvZ3JhbW1pbmcgYXMgYSANCnRlY2huaXF1ZSBmb3IgcGxhbm5pbmcgZXhwZW5kaXR1cmVzIGFuZCByZXR1cm5zIGluIG9yZGVyIHRvIG9wdGltaXplIGNvc3RzIHRvIHRoZSBhcm15IGFuZCBpbmNyZWFzZSBsb3NzZXMgdG8NCnRoZSBlbmVteS4gDQoNClRoZSBtZXRob2Qgd2FzIGtlcHQgc2VjcmV0IHVudGlsIDE5NDcsIHdoZW4gKipHZW9yZ2UgQi4gRGFudHppZyoqIHB1Ymxpc2hlZCB0aGUgKnNpbXBsZXgqIG1ldGhvZCBmb3Igc29sdmluZyBsaW5lYXIgcHJvZ3JhbW1pbmcNCg0KUm91Z2hseSBzcGVha2luZywgdGhlIGxpbmVhciBwcm9ncmFtbWluZyBwcm9ibGVtIGNvbnNpc3RzIGluIG9wdGltaXppbmcgKHRoYXQgaXMsIGVpdGhlciBtaW5pbWl6ZSBvciBtYXhpbWl6ZSkgdGhlIHZhbHVlIG9mIGEgbGluZWFyIG9iamVjdGl2ZSBmdW5jdGlvbiBvZiBhIHZlY3RvciBvZiBkZWNpc2lvbiB2YXJpYWJsZXMsIGNvbnNpZGVyaW5nIHRoYXQgdGhlIHZhcmlhYmxlcyBjYW4gb25seSB0YWtlIHRoZSB2YWx1ZXMgZGVmaW5lZCBieSBhIHNldCBvZiBsaW5lYXIgY29uc3RyYWludHMuIExpbmVhciBwcm9ncmFtbWluZyBpcyBhIGNhc2Ugb2YgbWF0aGVtYXRpY2FsIHByb2dyYW1taW5nLCB3aGVyZSBvYmplY3RpdmUgZnVuY3Rpb24gYW5kIGNvbnN0cmFpbnRzIGFyZSBsaW5lYXIuDQoNCkEgZm9ybXVsYXRpb24gb2YgYSBsaW5lYXIgcHJvZ3JhbSBpbiBpdHMgY2Fub25pY2FsIGZvcm0gb2YgbWF4aW11bSBpczoNCg0KJCRtYXggWiA9IFxzdW1fe2kgPSAxfSBeIG4gY19pIHhfaSQkDQogKipTdWJqZWN0IHRvICoqDQogDQogJCRcc3VtX3tpID0gaX0gXiBuIFxzdW1fe2ogPSBpfSBeIG0gYV97aWp9eF9pIFxsZSBiX2okJA0KICQkXHN1bV97aSA9IGl9IF4gbiBcc3VtX3tqID0gaX0gXiBtIGFfe2lqfXhfaSBcZ2UgYl9rJCQNCg0KICQkXHN1bV97aSA9IGl9IF4gbiBcc3VtX3tqID0gaX0gXiBtIGFfe2lqfXhfaSA9IGJfbCQkDQoNClRoZSBtb2RlbCBoYXMgdGhlIGZvbGxvd2luZyBlbGVtZW50czoNCg0KMS4gQW4gb2JqZWN0aXZlIGZ1bmN0aW9uIG9mIHRoZSBuIGRlY2lzaW9uIHZhcmlhYmxlcyAkeF9pJCAuIERlY2lzaW9uIHZhcmlhYmxlcyBhcmUgYWZmZWN0ZWQgYnkgdGhlIGNvc3QgY29lZmZpY2llbnRzICRjX2okDQoNCjIuIEEgc2V0IG9mIG0gY29uc3RyYWludHMsIGluIHdoaWNoIGEgbGluZWFyIGNvbWJpbmF0aW9uIG9mIHRoZSB2YXJpYWJsZXMgYWZmZWN0ZWQgYnkgY29lZmZpY2llbnRzICRhX3tpan0kIA0KaGFzIHRvIGJlIGxlc3Mgb3IgZXF1YWwgdGhhbiBpdHMgcmlnaHQgaGFuZCBzaWRlIHZhbHVlICRiX2kkIChjb25zdHJhaW50cyB3aXRoIHNpZ25zIGdyZWF0ZXIgb3IgZXF1YWwgb3INCmVxdWFsaXRpZXMgYXJlIGFsc28gcG9zc2libGUpDQoNCjMuIFRoZSBib3VuZHMgb2YgdGhlIGRlY2lzaW9uIHZhcmlhYmxlcy4gSW4gdGhpcyBjYXNlLCBhbGwgZGVjaXNpb24gdmFyaWFibGVzIGhhdmUgdG8gYmUgbm9ubmVnYXRpdmUNCg0KVGhlIExQIGZvcm11bGF0aW9uIHNob3duIGFib3ZlIGNhbiBiZSBleHByZXNzZWQgaW4gbWF0cml4IGZvcm0gYXMgZm9sbG93czoNCg0KJCRtYXggWiA9IFxtYXRoYmZ7Y31eVCBcbWF0aGJme3h9JCQNCg0KKipTdWJqZWN0IHRvKioNCg0KJCRcbWF0aGJme0F4ID0gYn0kJA0KDQokJFxtYXRoYmZ7eH0gXGdlIDAkJA0KDQojIyBNb2RlbGluZyBMaW5lYXIgUHJvZ3JhbXMNCg0KDQpMZXTigJlzIGNvbnNpZGVyIHRoZSBmb2xsb3dpbmcgc2l0dWF0aW9uOg0KDQpBIHNtYWxsIGJ1c2luZXNzIHNlbGxzIHR3byBwcm9kdWN0cywgbmFtZWQgUHJvZHVjdCAxIGFuZCBQcm9kdWN0IDIuIEVhY2ggdG9ubmUgb2YgUHJvZHVjdCAxIGNvbnN1bWVzIDMwIHdvcmtpbmcgaG91cnMsIGFuZCBlYWNoIHRvbm5lIG9mIFByb2R1Y3QgMiBjb25zdW1lcyAyMCB3b3JraW5nIGhvdXJzLiBUaGUgYnVzaW5lc3MgaGFzIGEgbWF4aW11bSBvZiAyNzAwIHdvcmtpbmcgaG91cnMgZm9yIHRoZSBwZXJpb2QgY29uc2lkZXJlZC4gQXMgZm9yIG1hY2hpbmUgaG91cnMsIGVhY2ggdG9ubmUgb2YgUHJvZHVjdHMgMSBhbmQgMiBjb25zdW1lcyA1IGFuZCAxMCBtYWNoaW5lIGhvdXJzLCByZXNwZWN0aXZlbHkuIFRoZXJlIGFyZSA4NTAgbWFjaGluZSBob3VycyBhdmFpbGFibGUuDQoNCkVhY2ggdG9ubmUgb2YgUHJvZHVjdCAxIHlpZWxkcyAyMCBNZSBvZiBwcm9maXQsIHdoaWxlIFByb2R1Y3QgMiB5aWVsZHMgNjAgTWUgZm9yIGVhY2ggdG9ubmUgc29sZC4gRm9yIHRlY2huaWNhbCByZWFzb25zLCB0aGUgZmlybSBtdXN0IHByb2R1Y2UgYSBtaW5pbXVtIG9mIDk1IHRvbm5lcyBpbiB0b3RhbCBiZXR3ZWVuIGJvdGggcHJvZHVjdHMuIFdlIG5lZWQgdG8ga25vdyBob3cgbWFueSB0b25uZXMgb2YgUHJvZHVjdCAxIGFuZCAyIG11c3QgYmUgcHJvZHVjZWQgdG8gbWF4aW1pemUgdG90YWwgcHJvZml0Lg0KDQpgYGB7ciBlY2hvPUZBTFNFfQ0KI1RhYmxlIDENCg0KdGFibGUxID0gZGF0YS5mcmFtZShWYXJpYWJsZSA9IGMoJ1dvcmtpbmcgSHJzJywgJ01hY2hpbmUgSHJzJywgJ1Byb2ZpdCcpLA0KICAgICAgICAgICAgICAgICAgICBQMSA9IGMoMzAsIDUsIDIwKSAsDQogICAgICAgICAgICAgICAgICAgIFAyID0gYygyMCwgMTAsIDYwKSwNCiAgICAgICAgICAgICAgICAgICAgQXZhaWxhYmxlID0gYygyNzAwLCA4NTAsIE5BKSkNCnRhYmxlMQ0KYGBgDQoNCg0KVGhpcyBzaXR1YXRpb24gaXMgYXB0IHRvIGJlIG1vZGVsZWQgYXMgYSBQTCBtb2RlbC4gRmlyc3QsIHdlIG5lZWQgdG8gZGVmaW5lIHRoZSBkZWNpc2lvbiB2YXJpYWJsZXMuIEluIHRoaXMgY2FzZSB3ZSBoYXZlOg0KDQoxLiAkeF8xJCBudW1iZXIgb2YgdG9ubmVzIHByb2R1Y2VkIGFuZCBzb2xkIG9mIFByb2R1Y3QgMQ0KMi4gJHhfMiQgbnVtYmVyIG9mIHRvbm5lcyBwcm9kdWNlZCBhbmQgc29sZCBvZiBQcm9kdWN0IDINCg0KVGhlIGNvc3QgY29lZmZpY2llbnRzIG9mIHRoZXNlIHZhcmlhYmxlcyBhcmUgMjAgYW5kIDYwLCByZXNwZWN0aXZlbHkuIFRoZXJlZm9yZSwgdGhlIG9iamVjdGl2ZSBmdW5jdGlvbiBpcyBkZWZpbmVkIG11bHRpcGx5aW5nIGVhY2ggdmFyaWFibGUgYnkgaXRzIGNvcnJlc3BvbmRpbmcgY29zdCBjb2VmZmljaWVudA0KDQokJG1heFogPSAyMCB4XzEgKyA2MCB4XzIkJA0KDQpBIGNvbnN0cmFpbnQgKipXSCoqIG1ha2luZyB0aGF0IHRoZSB0b3RhbCBhbW91bnQgb2Ygd29ya2luZyBob3VycyB1c2VkIGluIFByb2R1Y3QgMSBhbmQgUHJvZHVjdCAyLCANCndoaWNoIGVxdWFscyAkMzAgeF8xICsgMjAgeF8yJCwgaXMgbGVzcyBvciBlcXVhbCB0aGFuIDIsNzAwIGhvdXJzLg0KDQpBIHNpbWlsYXIgY29uc3RyYWludCBNSCBtYWtpbmcgdGhhdCB0aGUgdG90YWwgbWFjaGluZSBob3VycyAkNSB4XzEgKyAxMCB4XzIkIGFyZSBsZXNzIG9yIGVxdWFsIHRoYW4gODUwLg0KDQpBICBjb25zdHJhaW50IG1ha2luZyB0aGF0IHRoZSB0b3RhbCB1bml0cyBwcm9kdWNlZCBhbmQgc29sZCAkeF8xICsgeF8yJCBhcmUgZ3JlYXRlciBvciBlcXVhbCB0aGFuIDk1DQoNClB1dHRpbmcgYWxsIHRoaXMgdG9nZXRoZXIsIGFuZCBjb25zaWRlcmluZyB0aGF0IHRoZSBkZWNpc2lvbiB2YXJpYWJsZXMgYXJlIG5vbm5lZ2F0aXZlLCB0aGUgTFAgdGhhdCBtYXhpbWl6ZXMgcHJvZml0IGlzOg0KDQokJG1heFogPSAyMCB4XzEgKyA2MCB4XzIkJA0KDQoqKlN1YmplY3QgdG8qKg0KDQokJDMwIHhfMSArIDIwIHhfMiBcbGUgMjcwMCQkDQoNCiQkNSB4XzEgKyAxMCB4XzIgXGxlIDg1MCQkDQokJHhfMSArIHhfMiBcbGUgOTUgJCQNCiQkeF8xLCB4XzIgXGdlIDAkJA0KDQoNCg0KVGhlIGZvbGxvd2luZyBjb2RlIHNvbHZlcyB0aGF0IExQIHdpdGggdHdvIHZhcmlhYmxlczoNCg0KYGBge3IgZWNobz1UUlVFfQ0KcmVxdWlyZShscFNvbHZlKQ0KcmVxdWlyZSh0aWR5dmVyc2UpDQoNCiNkZWZpbmluZyBwYXJhbWV0ZXJzDQpvYmouZnVuIDwtIGMoMjAgLCA2MCkNCg0KY29uc3RyIDwtIG1hdHJpeCAoYygzMCAsIDIwLCA1LCAxMCwgMSwgMSkgLCBuY29sID0gMiwgYnlyb3cgPSBUUlVFKQ0KDQpjb25zdHIuZGlyIDwtIGMoIjw9IiwgIjw9IiwgIj49IikNCnJocyA8LSBjKDI3MDAgLCA4NTAgLCA5NSkNCg0KI3NvbHZpbmcgbW9kZWwNCnByb2Quc29sIDwtIGxwKCJtYXgiLCBvYmouZnVuLCBjb25zdHIsIGNvbnN0ci5kaXIsIHJocyAsIGNvbXB1dGUuc2VucyA9IFRSVUUgKQ0KDQpwcm9kLnNvbCRzb2x1dGlvbg0KDQpgYGANCg0KIyMjIEEgVHJhc3BvcnRhdGlvbiBQcm9ibGVtDQoNCkxldOKAmXMgY29uc2lkZXIgYSB0cmFuc3BvcnRhdGlvbiBwcm9ibGVtIG9mIHR3byBvcmlnaW5zICoqYSoqIGFuZCAqKmIqKiwgYW5kIHRocmVlIGRlc3RpbmF0aW9ucyAxLCAyIGFuZCAzLg0KSW4gdGhlIFRhYmxlIGJlbG93IGFyZSBwcmVzZW50ZWQgdGhlIGNvc3QgJGNfe2lqfSQgb2YgdHJhbnNwb3J0aW5nIG9uZSB1bml0IGZyb20gdGhlIG9yaWdpbiAkaSQgdG8gZGVzdGluYXRpb24gJGokLCBhbmQgdGhlIG1heGltYWwgY2FwYWNpdHkgb2YgdGhlIG9yaWdpbnMgYW5kIHRoZSByZXF1aXJlZCBkZW1hbmQgaW4gdGhlIGRlc3RpbmF0aW9ucy4NCg0KV2UgbmVlZCB0byBrbm93IGhvdyB3ZSBtdXN0IGNvdmVyIHRoZSBkZW1hbmQgb2YgdGhlIGRlc3RpbmF0aW9ucyBhdCBhIG1pbmltYWwgY29zdC4NCg0KYGBge3J9DQoNClRhYmxlMiA9IGRhdGEuZnJhbWUoUGFyYW1ldGVyID0gYygnT3JpZ2luIEEnLCAnT3JpZ2luIEInLCAnRGVtYW5kJyksDQogICAgICAgICAgICAgICAgICAgIERlc3QuMSA9IGMoOCwgMiwgNDApLA0KICAgICAgICAgICAgICAgICAgICBEZXN0LjIgPSBjKDYsIDQsIDM1KSwNCiAgICAgICAgICAgICAgICAgICAgRGVzdC4zID0gYygzLCA5LCAyNSksDQogICAgICAgICAgICAgICAgICAgIENhcGFjaXR5ID0gYyg3MCwgNDAsIE5BKSkNClRhYmxlMg0KDQpgYGANCg0KVGhpcyBzaXR1YXRpb24gY2FuIGJlIG1vZGVsZWQgd2l0aCBhIExQIHdpdGggdGhlIGZvbGxvd2luZyBlbGVtZW50czoNCg0KMS4gRGVjaXNpb24gdmFyaWFibGVzIG9mIHRoZSBmb3JtICR4X3tpan0gLCByZXByZXNlbnRpbmcgdW5pdHMgdHJhbnNwb3J0ZWRmcm9tIG9yaWdpbiAkaSQgdG8gZGVzdGluYXRpb24gJGokDQoyLiBBbiBvYmplY3RpdmUgZnVuY3Rpb24gd2l0aCBjb3N0IGNvZWZmaWNpZW50cyBlcXVhbCB0byAkY197aWp9JA0KMy4gVHdvIHNldHMgb2YgY29uc3RyYWludHM6IGEgbGVzcyBvciBlcXVhbCBzZXQgb2YgY29uc3RyYWludHMgZm9yIGVhY2ggb3JpZ2luLCBsaW1pdGluZyB0aGUgdW5pdHMgdG8gYmUgdHJhbnNwb3J0ZWQsIGFuZCBhIGdyZWF0ZXIgb2YgZXF1YWwgc2V0IG9mIGNvbnN0cmFpbnRzIHJlcHJlc2VudGluZyB0aGF0IHRoZSBkZW1hbmQgb2YgZWFjaCBkZXN0aW5hdGlvbiBtdXN0IGJlIGNvdmVyZWQuDQoNClRoZSByZXN1bHRpbmcgTFAgaXM6DQoNCiQkbWF4WiA9IDh4X3thMX0gKyA2eF97YTJ9ICsgM3hfe2EzfSArIDJ4X3tiMX0gKyA0eF97YjJ9ICsgOXhfe2IzfSQkDQoNCioqU3ViamVjdCB0bzoqKg0KDQokJHhfe2ExfSArIHhfe2EyfSArIHhfe2EzfSBcbGUgNzAkJA0KJCR4X3tiMX0gKyB4X3tiMn0gKyB4X3tiM30gXGxlIDQwJCQNCiQkeF97YTF9ICsgeF97YjF9IFxnZSA0MCQkDQokJHhfe2EyfSArIHhfe2IyfSBcZ2UgMzUkJA0KJCR4X3thM30gKyB4X3tiM30gXGdlIDI1JCQNCg0KJCR4X3tpan0gXGdlIDAkJA0KDQpgYGB7ciBlY2hvPVRSVUV9DQpsaWJyYXJ5ICggbHBTb2x2ZSApDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCiNkZWZpbmluZyBwYXJhbWV0ZXJzDQojb3JpZ2lucyBydW4gaSBpbiAxOm0NCg0KI2Rlc3RpbmF0aW9ucyBydW4gaiBpbiAxOm4NCm9iai5mdW4gPC0gYyg4LCA2LCAzLCAyLCA0LCA5KQ0KDQptIDwtIDIgI251bWJlciBvZiBPcmlnaW5zDQpuIDwtIDMgI251bWJlciBvZiBkZXN0aW5pZXMNCg0KDQojQ3JlYXRlIGEgTWF0cml4IG9mIENvbnN0cmFpbnRzIG4gKyBtIHJvd2EgYW5kICBuIHggbSBjb2x1bW5zDQpjb25zdHIgPC0gbWF0cml4ICgwLCBuICsgbSwgbiAqIG0pDQpmb3IgKGkgaW4gMSA6IG0pIHsNCiAgZm9yIChqIGluIDEgOiBuKSB7DQogICAgY29uc3RyIFtpLCBuICogKGkgLTEpICsgal0gPC0gMQ0KICAgIGNvbnN0ciBbbSArIGosIG4gKiAoaSAtIDEpICsgal0gPC0gMQ0KICB9DQp9DQoNCg0KY29uc3RyLmRpciA8LSBjKHJlcCgiPD0iLCBtKSwgcmVwKCI+PSIsIG4pKQ0KDQpyaHMgPC0gYyg3MCAsIDQwLCA0MCwgMzUsIDI1KQ0KDQojc29sdmluZyBMUCBtb2RlbA0KcHJvZC50cmFucyA8LSBscCgibWluIiwgb2JqLmZ1biAsIGNvbnN0ciAsIGNvbnN0ci5kaXIgLCByaHMgLCBjb21wdXRlLnNlbnMgPSBUUlVFICkNCg0KI0xQIHNvbHV0aW9uDQpwcm9kLnRyYW5zICRvYmoudmFsDQoNCnNvbCA8LSBtYXRyaXggKCBwcm9kLnRyYW5zJHNvbHV0aW9uICwgbSwgbiwgYnlyb3cgPSBUUlVFICkNCnNvbA0KDQpgYGANCg0KIyMjIFRyYW5zcG9ydGF0aW9uIGJ5IFRydWtzDQoNCkluIFRhYmxlMyBjYW4gYmUgZm91bmQgdGhlIHF1YXJ0ZXJseSBkZW1hbmQgKGluIHRvbm5lcykgYW5kIHRoZSBhY3F1aXNpdGlvbiBjb3N0cyBwZXIgdG9ubmUgKGluIGtlIHBlciB0b25uZSkgZm9yIGVhY2ggcXVhcnRlciBvZiByYXcgbWF0ZXJpYWxzIGZvciBhIGNoZW1pY2FsIHBsYW50LiBBbGwgcHVyY2hhc2VzIGluIGEgZ2l2ZW4gcXVhcnRlciBjYW4gYmUgdXNlZCB0byBjb3ZlciB0aGUgZGVtYW5kIG9mIHRoZSBwcmVzZW50IHF1YXJ0ZXIsIG9yIHRoZSBkZW1hbmQgb2YgcXVhcnRlcnMgaW4gdGhlIGZ1dHVyZS4gVGhlIGNvc3RzIG9mIHN0b2NraW5nIGFyZSBvZiANCjhrIFVTRCBwZXIgdG9ubmUgc3RvcmVkIGF0IHRoZSBlbmQgb2YgZWFjaCBxdWFydGVyLiBUaGUgc3RvY2tzIGF0IHRoZSBiZWdpbm5pbmcgb2YgZmlyc3QgcXVhcnRlciBhcmUgb2YgMTAwIHRvbm5lcywgYW5kIGl0IGlzIG5lZWRlZCB0aGUgc2FtZSBhbW91bnQgb2Ygc3RvY2sgYXQgdGhlIGVuZCBvZiB0aGUgZm91cnRoIHF1YXJ0ZXIuDQoNCkluIGFkZGl0aW9uIHRvIHRoZSBwdXJjaGFzZSBhbmQgc3RvcmFnZSBjb3N0cywgdGhlIHRyYW5zcG9ydGF0aW9uIGNvc3RzIGhhdmUgdG8gYmUgY29uc2lkZXJlZC4gQWxsIHRoZSBwdXJjaGFzZWQgcXVhbnRpdHkgb2YgcmF3IG1hdGVyaWFscyBoYXMgdG8gYmUgdHJhbnNwb3J0ZWQsIHVzaW5nIGFueSBjb21iaW5hdGlvbiBvZiB0aGUgdHdvIGF2YWlsYWJsZSB0cnVjaw0KbW9kZWxzOg0KDQoxLiBTbWFsbCB0cnVja3M6IGNvc3Qgb2YgNzAwayB1U0QsIGFuZCBjYXBhY2l0eSBvZiA1MDAgdG9ubmVzLg0KMi4gTGFyZ2UgdHJ1Y2tzOiBjb3N0IG9mIDEsNDAwayBVU0QsIGFuZCBjYXBhY2l0eSBvZiAxLDIwMCB0b25uZXMuDQoNCldlIG5lZWQgdG8gZGVmaW5lIGEgbGluZWFyIHByb2dyYW1taW5nIG1vZGVsIHRoYXQgYWxsb3dzIHRoZSBtaW5pbWl6YXRpb24gb2YgdGhlIHRvdGFsIGNvc3RzOiBhY3F1aXNpdGlvbiwgc3RvcmFnZSBhbmQgdHJhbnNwb3J0LCBvYnRhaW5pbmcgdGhlIGFtb3VudCByYXcgbWF0ZXJpYWxzIHRvIHB1cmNoYXNlLCBhbmQgdGhlIGFtb3VudCBvZiB0cnVja3Mgb2YgYm90aCBraW5kcyB0byBiZSBjb250cmFjdGVkIGVhY2ggcXVhcnRlci4NCg0KYGBge3J9DQpUYWJsZTMgPSBkYXRhLmZyYW1lKFF1YXJ0ZXIgPSBjKCdEZW1hbmQnLCAnVW5pdCBDb3N0JyksDQogICAgICAgICAgICAgICAgICAgIFQxID0gYygxMDAwLCAyMCkgLA0KICAgICAgICAgICAgICAgICAgICBUMiA9IGMoMTIwMCwgMjUpLA0KICAgICAgICAgICAgICAgICAgICBUMyA9IGMoMTUwMCwgMzApLA0KICAgICAgICAgICAgICAgICAgICBUNCA9IGMoMTgwMCwgNDApKQ0KDQpUYWJsZTMNCmBgYA0KDQoNClRoZSB2YXJpYWJsZXMgdG8gZGVmaW5lIGFyZToNCg0KMS4gJHFfaSQgY29udGludW91czogdG9ubmVzIG9mIHJhdyBtYXRlcmlhbCB0byBwdXJjaGFzZSBpbiBxdWFydGVyIGkNCjIuICRzX2kkIGNvbnRpbnVvdXM6IHRvbm5lcyBpbiBzdG9jayBhdCB0aGUgZW5kIG9mIHF1YXJ0ZXIgaSwgYW5kICRzXzAkIGFzIHRoZSBpbml0aWFsIHN0b2NrDQozLiAkdF9pJCBpbnRlZ2VyOiBzbWFsbCB0cnVja3MgdG8gY29udHJhY3QgaW4gcXVhcnRlciBpDQo0LiAkdV9pJCBpbnRlZ2VyOiBsYXJnZSB0cnVja3MgdG8gY29udHJhY3QgaW4gcXVhcnRlciBpDQoNCk9uY2UgZGVmaW5lZCB0aGUgdmFyaWFibGVzLCB0d28gc2V0cyBvZiBjb25zdHJhaW50cyBoYXZlIHRvIGJlIGRlZmluZWQ6DQoNCjEuIENvbnN0cmFpbnRzIGFzc3VyaW5nIHRoYXQgdGhlIHB1cmNoYXNlIHBsYW4gbWVldHMgdGhlIGRlbWFuZCBjb21taXRtZW50cy4gVGhlc2UgYXJlIG9mIHRoZSBmb3JtICRzX3tpLTF9ICsgcV9pIC0gc19pID0gZF9pJCwgYmVpbmcgJGRfaSQgdGhlIGRlbWFuZCBvZiB0aGUgcXVhcnRlci4NCjIuIENvbnN0cmFpbnRzIGFzc3VyaW5nIHRoYXQgYSBzdWZmaWNpZW50IG51bWJlciBvZiBlYWNoIGtpbmQgb2YgdHJ1Y2tzIGlzIGNvbnRyYWN0ZWQ6ICRxX2kgXGxlIDUwMHRfaSArIDEyMDA0IHVfaSQNCg0KVGhlIHJlc3VsdGluZyBtb2RlbCBpczoNCg0KJCRtaW5aID0gXHN1bV97aSA9IDF9IF4gNCAoY19pcV9pICsgOHNfaSArIDcwMHRfaSArIDE0MDB1X2kpICQkDQoqKlN1YmplY3QgdG86KioNCg0KJCRzX3tpLTF9ICsgcV9pIC0gc19pID0gZF9pIFxSaWdodGFycm93XGZvcmFsbCBpID0gMSwuLi4sNCQkDQokJHFfaSAtIDUwMHRfaSAtIDEyMDB1aSBcbGUgMCBcUmlnaHRhcnJvd1xmb3JhbGwgaSA9IDEsLi4uLDQkJA0KDQokJHNfMCA9IHNfNCA9IDEwMCQkDQoNCiQkc19pLCBxX2kgXGdlIDAkJA0KDQpgYGB7ciBlY2hvPVRSVUV9DQpsaWJyYXJ5ICggbHBTb2x2ZSApDQojZGVmaW5pbmcgcGFyYW1ldGVycw0KI29yaWdpbnMgcnVuIGkgaW4gMTptDQoNCiN2ZWN0b3Igb2YgQ29zdCBjb2VmaWNpZW50cyBmb3IgZWFjaCBvZiBxaSwgc2ksIHRpIGFuZCB1aSBjb25haW5pbmcgNCB4IDQgPSAxNiBjb2VmaWNpZW50cw0Kb2JqLmZ1biA8LSBjKDIwLCAyNSwgMzAsIDQwLA0KICAgICAgICAgICAgIDAsIDgsIDgsIDgsIDgsDQogICAgICAgICAgICAgNzAwLCA3MDAsIDcwMCwgNzAwLA0KICAgICAgICAgICAgIDE0MDAsIDE0MDAsIDE0MDAsIDE0MDApDQoNCg0KI0luIHRoaXMgY2FzZSwgd2UgaGF2ZSBkZXNpZ24gdGhlIG1vZGVsIE1hdHJpeCBpbiBhbiBleGNlbCBzcHJlYWRzaGVldA0KZGF0YS50cnVjayA9IHJlYWQuY3N2KCdUcnVjayBEaXN0cmlidXRpb24gUHJvYmxlbS5jc3YnKQ0KZGF0YS50cnVjaw0KDQojVGFraW5nIHRoZSBjb25zdHJhaW50IHBpZWNlDQpjb25zdHIgPSBkYXRhLnRydWNrWywgMiA6IChuY29sKGRhdGEudHJ1Y2spIC0gMSldDQpjb25zdHIgPSBhcy5tYXRyaXgoY29uc3RyKQ0KY29uc3RyDQoNCiNHZXR0aW5nIHRoZSByaWdodCBoYW5kIHNpZGUgb2YgdGhlIGVjdWF0aW9ucw0KDQpyaHMgPC0gZGF0YS50cnVja1ssIG5jb2woZGF0YS50cnVjayldDQpyaHMgPC0gYXMubnVtZXJpYyhyaHMpDQoNCg0KI0NyZWF0aW5nIHRoZSBjb25zdHJhaW50IGRpcmVjdGlvbiB2ZWN0b3IuIFRoZSBmaXJ0cyA2IGNvbnN0cnNpbnRzIGFyZSAiPSIgdGhlIG90aGVyIDQgYXJlICI8PSINCg0KY29uc3RyLmRpciA8LSBjKHJlcCgiPSIsIDYpLCByZXAoIjw9IiwgNCkpDQoNCg0KI3NvbHZpbmcgTFAgbW9kZWwNCnByb2QudHJhbnMgPC0gbHAoIm1pbiIsIG9iai5mdW4gLCBjb25zdHIgLCBjb25zdHIuZGlyICwgcmhzICwgY29tcHV0ZS5zZW5zID0gVFJVRSApDQpwcm9kLnRyYW5zJHNvbHV0aW9uDQoNCg0KYGBgDQoNCkluIHRoaXMgY2FzZSwgdGhlIHNvbHV0aW9uIGhhcyBzb21lIHByYWN0aWNhbCBhbmQgdGVjaG5pY2FsIGlzc3Vlcy4gV2Ugc3VwcG9zZWQgdG8gaGF2ZSBvbmx5IGludGVnZXIgc29sdXRpb25zDQphbmQgaW4gdGhlIGNhc2Ugb2YgJHVfMSA9IDAuNzUkIGFuZCAkdV8zID0gMi44MyQgdGhpcyBjb25kaXRpb24gaXMgbm90IHRydWUuDQoNClRvIGZpeCB0aGlzLCB3ZSB1c2UgKipJbnRlZ2VyIFByb2dyYW1taW5nIG1vZGVscyoqIGVuc3VyaW5nIGFsbCB2YXJpYWJsZXMgdG8gYmUgaW50ZWdlcjoNCg0KYGBge3IgZWNobz1UUlVFfQ0KcHJvZC50cmFucyA8LSBscCgibWluIiwgb2JqLmZ1biAsIGNvbnN0ciAsIGNvbnN0ci5kaXIgLCByaHMgLCBjb21wdXRlLnNlbnMgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgYWxsLmludCA9IFRSVUUpDQpwcm9kLnRyYW5zJHNvbHV0aW9uDQoNCiNDcmVhdGUgYSBkYXRhIGZyYW1lIHdpdGggdGhlIHNvbHV0aW9ucw0KcHJvZC50cmFucyA9IGRhdGEuZnJhbWUoVmFyaWFibGUgPSBuYW1lcyhkYXRhLnRydWNrKVsyIDogKG5jb2woZGF0YS50cnVjaykgLSAxKV0sDQogICAgICAgICAgICAgICAgICAgICAgICBTb2x1dGlvbiA9IHByb2QudHJhbnMkc29sdXRpb24pDQpwcm9kLnRyYW5zDQoNCmBgYA0KDQoNCiMjIyBIaXJpbmcgJiBGaXJpbmcNCg0KSW4gVGFibGU0IGFyZSBsaXN0ZWQgdGhlIG5lZWRzIG9mIHBpbG90cyBhYmxlIHRvIGZsaWdodCBhbiBBMzIwIGZvciB0aGUgZm9sbG93aW5nIHNpeCBtb250aHMuIFRoZSBjb3N0IG9mIGEgcGlsb3TigJlzIHNhbGFyeSBpcyAkOGsgVVNEIHBlciBtb250aC4gQXQgdGhlIGJlZ2lubmluZyBvZiBNb250aCAxIHRoZSBhaXJsaW5lIGhhcyBhIHN0YWZmIG9mIDIwIHBpbG90cywgYnV0IHRoaXMgc3RhZmYgY2FuIGJlIGFkanVzdGVkIGVhY2ggbW9udGguDQoNClBpbG90cyBjYW4gYmUgaGlyZWQgYW5kIGZpcmVkIGF0IHRoZSBiZWdpbm5pbmcgb2YgZWFjaCBtb250aC4gTmV3bHkgaGlyZWQgcGlsb3RzIGNhbiBzdGFydCB3b3JraW5nIGF0IA0KdGhlIHNhbWUgbW9udGgsIGFuZCBmaXJlZCBwaWxvdHMgc3RvcCB3b3JraW5nIHRoZSBzYW1lIGRheSB0aGV5IGFyZSBmaXJlZC4gVGhlIGNvc3Qgb2YgZmlyaW5nIGEgcGlsb3QgaXMgDQokMTBrIFVTRCwgYW5kIHRoZSBoaXJpbmcgY29zdCBpcyBvZiA1SyBVU0QgcGVyIHBpbG90LiBJZiBpdCBpcyBjb252ZW5pZW50LCB0aGUgYWlybGluZQ0KY2FuIGhhdmUgYSBzdGFmZiBvZiBwaWxvdHMgbGFyZ2VyIHRoYW4gdGhlIGFjdHVhbCBuZWVkcy4NCg0KMS4gRGVmaW5lIGEgbGluZWFyIHByb2dyYW1taW5nIG1vZGVsIHRvIG9idGFpbiB0aGUgcGlsb3RzIHRvIGhpcmUgYW5kIGZpcmUgZWFjaCBtb250aCB0byBtaW5pbWl6ZSB0aGUgdG90YWwgY29zdCBvZiBwaWxvdCBzdGFmZiAoY29zdHMgb2Ygc2FsYXJ5IHBsdXMgaGlyaW5nIGFuZCBmaXJpbmcgY29zdHMpLg0KDQoNClRvIG1vZGVsIHRoaXMgc2l0dWF0aW9uLCB3ZeKAmWxsIGhhdmUgdG8gZGVmaW5lIHRoZSBmb2xsb3dpbmcgdmFyaWFibGVzOg0KDQoxLiBWYXJpYWJsZXMgJGhfaSQ6IHBpbG90cyBoaXJlZCBhdCB0aGUgYmVnaW5uaW5nIG9mIG1vbnRoIGkNCjIuIFZhcmlhYmxlcyAkZl9pJDogcGlsb3RzIGZpcmVkIGF0IHRoZSBiZWdpbm5pbmcgb2YgbW9udGggaQ0KMy4gVmFyaWFibGVzICRzX2kkOiBzdGFmZiBvZiBwaWxvdHMgZHVyaW5nIG1vbnRoIGkNCg0KVGhlIG1vZGVsIHNob3VsZCBoYXZlIHRoZSBmb2xsb3dpbmcgZ3JvdXBzIG9mIGNvbnN0cmFpbnRzOg0KDQoxLiBDb25zdHJhaW50cyBhc3N1cmluZyB0aGF0IHRoZSBzdGFmZiBvZiBwaWxvdHMgYXQgdGhlIGJlZ2lubmluZyBvZiBtb250aCBpIGlzIGVxdWFsIHRvICRzX2kgPSBoX2kgLSBmX2kgKyBzX3tpLTF9JC4gSW4gdGhpcyBjYXNlLCB3ZSBoYXZlIHRoYXQgJHNfMCA9IDIwJC4NCjIuIENvbnN0cmFpbnRzIGFzc3VyaW5nIHRoYXQgdmFyaWFibGVzICRzX2kkIGFyZSBiaWdnZXIgb2YgZXF1YWwgdG8gdGhlIHZhbHVlcyBvZiBzdGFmZiByZXF1aXJlZCBkaSBsaXN0ZWQgaW4gVGFibGU0Lg0KDQpgYGB7cn0NCnRhYmxlNCA9IGRhdGEuZnJhbWUoTW9udGhzID0gYygnbTEnLCAnbTInLCAnbTMnLCAnbTQnLCAnbTUnLCAnbTYnKSwNCiAgICAgICAgICAgICAgICAgICAgUGlsb3RzTmVlZCA9IGMoMzAsIDYwLCA1NSwgNDAsIDQ1LCA1MCkpDQp0YWJsZTQNCmBgYA0KDQpUaGUgbGluZWFyIHByb2JsZW0gdG8gYmUgc29sY2VkIGlzOg0KDQokJG1pblogPSA1XHN1bV97aSA9IDF9IF4gNiBoX2kgKyAxMFxzdW1fe2kgPSAxfSBeIDYgZl9pICsgOFxzdW1fe2kgPSAxfSBeIDYgc19pJCQNCg0KKipTdWJqZWN0IHRvKioNCg0KJCRzX2kgPSBoX2kgLSBmX2kgKyBzX3tpLTF9IFxSaWdodGFycm93XGZvcmFsbCBpID0gMSwuLi4sNiQkDQokJHNfaSBcZ2UgZF9pIFxSaWdodGFycm93XGZvcmFsbCBpID0gMSwuLi4sNiQkDQokJGhfaSwgZl9pLCBzX2kgXGdlIDAkJA0KDQpgYGB7ciBlY2hvPVRSVUV9DQojVmFyaWFibGUgbmFtZXMNCnZhci5uYW1lcyA9IGMoJ2gxJywgJ2gyJywgJ2gzJywgJ2g0JywgJ2g1JywgJ2g2JywNCiAgICAgICAgICAgICAgICAgICdmMScsICdmMicsICdmMycsICdmNCcsICdmNScsICdmNicsDQogICAgICAgICAgICAgICAgICAnczAnLCAnczEnLCAnczInLCAnczMnLCAnczQnLCAnczUnLCAnczYnKQ0KDQoNCiNkZXN0aW5hdGlvbnMgcnVuIGogaW4gMTpuDQpvYmouZnVuIDwtIGMocmVwKDUsIDYpLCByZXAoMTAsIDYpLCAwLCByZXAoOCwgNikpDQpuYW1lcyhvYmouZnVuKSA9IGMoJ2gxJywgJ2gyJywgJ2gzJywgJ2g0JywgJ2g1JywgJ2g2JywNCiAgICAgICAgICAgICAgICAgICdmMScsICdmMicsICdmMycsICdmNCcsICdmNScsICdmNicsDQogICAgICAgICAgICAgICAgICAnczAnLCAnczEnLCAnczInLCAnczMnLCAnczQnLCAnczUnLCAnczYnKQ0KDQptIDwtIGxlbmd0aChvYmouZnVuKSAjbnVtYmVyIG9mIHZhcnMNCm4gPC0gNyAjbnVtYmVyIG9mIG1vbnRocyArIDENCg0KY29uc3RyMSA8LSBtYXRyaXggKDAsIG5yb3cgPSBuLCBuY29sID0gbSkNCmNvbG5hbWVzKGNvbnN0cjEpID0gdmFyLm5hbWVzDQoNCiNDcmVhdGUgbW9kZWwgZm9yIENvbnN0cmFpbnQgMTogc1tpLTFdIC0gc1tpXSAtIGZbaV0gKyBoW2ldID0gMA0KDQpjb25zdHIxWzEgOiA2LCAxIDogNl0gPSBkaWFnKHggPSAxLCBuY29sID0gNiwgbnJvdyA9IDYpDQpjb25zdHIxWzEgOiA2LCA3IDogMTJdID0gZGlhZyh4ID0gLTEsIG5jb2wgPSA2LCBucm93ID0gNikNCmNvbnN0cjFbMSA6IDYsIDEzIDogMThdICA9IGRpYWcoeCA9IDEsIG5jb2wgPSA2LCBucm93ID0gNikNCmk9IDENCmogPSAxNA0KZm9yKGkgaW4gMSA6IChuIC0gMSkpew0KICAgIGNvbnN0cjFbaSwgal0gPSAtMQ0KICAgIGogPSBqICsgMQ0KfQ0KY29uc3RyMVs3LCAxM10gID0gMQ0KDQojQ3JlYXRlIHNlY29uZCBjb25zdHJhaW50IG9uIG1pbmltdW4gcmVxdWlyZW1lbnRzIHNbaV0gPiA9IGRbaV0NCmNvbnN0cjIgPC0gbWF0cml4ICgwLCBucm93ID0gbiAtIDEsIG5jb2wgPSBtKQ0KY29uc3RyMlsxIDogNiwgMTQgOiAxOV0gPSBkaWFnKHggPSAxLCBuY29sID0gNiwgbnJvdyA9IDYpDQoNCiNCaW5kIENvbnN0cjEgYW5kIENvbnNyMiBpbiBhIHNpbmdsZSBtYXRyaXgNCmNvbnN0ciA9IHJiaW5kKGNvbnN0cjEsIGNvbnN0cjIpDQoNCiNDcmVhdGUgUkhTIGNvbnNyYWludGluIGEgdmVjdG9yIG9mIDEgcm93IHggMTMgcm93cw0KcmhzID0gYyhyZXAoMCwgNiksIDIwLCAzMCwgNjAsIDU1LCA0MCwgNDUsIDUwKQ0KDQojQ3JlYXRlIGRpcmVjdGlvbg0KY29uc3RyLmRpciA8LSBjKHJlcCgiPSIsIDcpLCByZXAoIj49IiwgNikpDQoNCiNTb2x2ZSBQcm9ibGVtDQpzdGFmZmluZyA9IGxwKCdtaW4nLCBvYmouZnVuLCBjb25zdHIsIGNvbnN0ci5kaXIsIHJocykNCg0KI1NvbHV0aW9uDQpzdGFmZmluZyA9IHN0YWZmaW5nJHNvbHV0aW9uDQpzdGFmZmluZyA9IGRhdGEuZnJhbWUoVmFyLk5hbWUgPSB2YXIubmFtZXMsIFNvbHV0aW9uID0gc3RhZmZpbmcpDQpzdGFmZmluZw0KDQpgYGANCg0KMi4gTW9kaWZ5IHRoZSBsaW5lYXIgbW9kZWwgdG8gaW5jbHVkZSB0aGUgY29uc3RyYWludCB0aGF0IHRoZSBhaXJsaW5lIGNhbm5vdCBmaXJlIHBpbG90cyBpZiBpdCBoYXMgaGlyZWQgcGlsb3RzIA0KdGhlIHByZXZpb3VzIG1vbnRoLg0KDQpMb29raW5nIGF0IHRoZSBzb2x1dGlvbiBvZiB0aGUgcHJldmlvdXMgcHJvYmxlbSwgaXQgY2FuIGJlIHNlZW4gdGhhdCB0aGlzIG5ldyBjb25zdHJhaW50IGRvZXMgbm90IGhvbGQgZm9yIA0KbW9udGhzIDIgYW5kIDM6IGluIG1vbnRoIDIgYXJlIGhpcmVkIDMwIHBpbG90cywgYW5kIGluIG1vbnRoIDMgYXJlIGZpcmVkIDUgcGlsb3RzLiBUaGVuIGEgbmV3IG1vZGVsIGhhcyB0byANCmJlIGRlZmluZWQgdG8gYWNjb3VudCBmb3IgdGhpcyBuZXcgcmVzdHJpY3Rpb24uIFRvIGRvIHNvLCB3ZSBoYXZlIHRvIGFkZCBhIG5ldyBiaW5hcmkgdmFyaWFibGU6DQoNCiogVmFyaWFiZSAkYl9pJDogZXF1YWxzIG9uZSBpZiBwaWxvdHMgYXJlIGhpcmVkIGluIG1vbnRoIGksIGFuZCB6ZXJvIG90aGVyd2lzZQ0KDQpUaGVuLCB0d28gbmV3IHNldHMgb2YgY29uc3RyYWludHMgbXVzdCBiZSBhZGRlZDogb25lIHNldCBhc3N1cmluZyB0aGF0ICRiX2kgPSAwIFxSaWdodGFycm93IGZfaSA9IDAkLCBhbmQgDQphbm90aGVyIHNldCBtYWtpbmcgdGhhdCAkYl9pID0gMSBcUmlnaHRhcnJvdyBmX3tpKzF9ID0gMCQNCg0KVGhlIGxpbmVhciBwcm9ibGVtIHRvIGJlIHNvbGNlZCBpczoNCg0KJCRtaW5aID0gNVxzdW1fe2kgPSAxfSBeIDYgaF9pICsgMTBcc3VtX3tpID0gMX0gXiA2IGZfaSArIDhcc3VtX3tpID0gMX0gXiA2IHNfaSQkDQoNCioqU3ViamVjdCB0byoqDQoNCiQkc19pID0gaF9pIC0gZl9pICsgc197aS0xfSBcUmlnaHRhcnJvd1xmb3JhbGwgaSA9IDEsLi4uLDYkJA0KJCRzX2kgXGdlIGRfaSBcUmlnaHRhcnJvd1xmb3JhbGwgaSA9IDEsLi4uLDYkJA0KJCRmX2kgXGxlIE1iX2kgXFJpZ2h0YXJyb3cgXGZvcmFsbCBpID0gMSwuLi4sNSQkDQokJGhfe2krMX0gXGxlIE0oMSAtIGJfaSkgXFJpZ2h0YXJyb3cgXGZvcmFsbCBpID0gMSwuLi4sNSQkDQoNCiQkYl9pIFxpbiBbMCwgMV0kJA0KJCRoX2ksIGZfaSwgc19pIFxnZSAwJCQNCg0KVGhlIGxldHRlciAkTSQgcmVwcmVzZW50cyBhbiBhcnRpZml0aWFsIHBlbmFsdHkgYW5kIGlzIHVzdWFsbHkgYXN1bWVkIHRvIGJlIGEgYmlnIG51bWJlci4gSW4gdGhlIGNvbnRleHQgb2YNCnRoZSBwcm9ibGVtLCAkTT0xMDAwJCBpcyBiaWcuDQoNCg0KYGBge3IgZWNobz1UUlVFfQ0KI1ZhcmlhYmxlIG5hbWVzDQoNCk0gPSAxMDAwICNQZW5hbHR5IFZhcmlhYmxlDQoNCnZhci5uYW1lcyA9IGMoJ2gxJywgJ2gyJywgJ2gzJywgJ2g0JywgJ2g1JywgJ2g2JywNCiAgICAgICAgICAgICAgICAgICdmMScsICdmMicsICdmMycsICdmNCcsICdmNScsICdmNicsDQogICAgICAgICAgICAgICAgICAnczAnLCAnczEnLCAnczInLCAnczMnLCAnczQnLCAnczUnLCAnczYnLA0KICAgICAgICAgICAgICAnYjEnLCAnYjInLCAnYjMnLCAnYjQnLCAnYjUnKQ0KDQoNCiNkZXN0aW5hdGlvbnMgcnVuIGogaW4gMTpuDQpvYmouZnVuIDwtIGMocmVwKDUsIDYpLCByZXAoMTAsIDYpLCAwLCByZXAoOCwgNiksIHJlcCgwLCA1KSkNCm5hbWVzKG9iai5mdW4pID0gdmFyLm5hbWVzDQoNCm0gPC0gbGVuZ3RoKG9iai5mdW4pICNudW1iZXIgb2YgdmFycw0KbiA8LSA3ICNudW1iZXIgb2YgbW9udGhzICsgMQ0KDQojQ3JlYXRlIG1vZGVsIGZvciBDb25zdHJhaW50IDE6IHNbaS0xXSAtIHNbaV0gLSBmW2ldICsgaFtpXSA9IDANCmNvbnN0cjEgPC0gbWF0cml4ICgwLCBucm93ID0gbiwgbmNvbCA9IG0pDQpjb2xuYW1lcyhjb25zdHIxKSA9IHZhci5uYW1lcw0KDQoNCmNvbnN0cjFbMSA6IDYsIDEgOiA2XSA9IGRpYWcoeCA9IDEsIG5jb2wgPSA2LCBucm93ID0gNikNCmNvbnN0cjFbMSA6IDYsIDcgOiAxMl0gPSBkaWFnKHggPSAtMSwgbmNvbCA9IDYsIG5yb3cgPSA2KQ0KY29uc3RyMVsxIDogNiwgMTMgOiAxOF0gID0gZGlhZyh4ID0gMSwgbmNvbCA9IDYsIG5yb3cgPSA2KQ0KaT0gMQ0KaiA9IDE0DQpmb3IoaSBpbiAxIDogKG4gLSAxKSl7DQogICAgY29uc3RyMVtpLCBqXSA9IC0xDQogICAgaiA9IGogKyAxDQp9DQpjb25zdHIxWzcsIDEzXSAgPSAxDQoNCiNDcmVhdGUgc2Vjb25kIGNvbnN0cmFpbnQgb24gbWluaW11biByZXF1aXJlbWVudHMgc1tpXSA+ID0gZFtpXQ0KY29uc3RyMiA8LSBtYXRyaXggKDAsIG5yb3cgPSBuIC0gMSwgbmNvbCA9IG0pDQpjb25zdHIyWzEgOiA2LCAxNCA6IDE5XSA9IGRpYWcoeCA9IDEsIG5jb2wgPSA2LCBucm93ID0gNikNCg0KI0NyZWF0ZSB0aGUgM3JkIHNldCBvZiBjb25zdHJhaW50cyBmW2ldIC0gTSBiW2ldIDw9IDANCmNvbnN0cjMgPSBtYXRyaXgoMCwgbnJvdyA9IDUsIG5jb2wgPSBtKQ0KY29sbmFtZXMoY29uc3RyMykgPSB2YXIubmFtZXMNCg0KY29uc3RyM1ssIDcgOiAxMV0gPSBkaWFnKDEsIG5yb3cgPSA1LCBuY29sID0gNSkNCmNvbnN0cjNbLCAyMCA6IDI0XSA9IGRpYWcoLU0sIG5yb3cgPSA1LCBuY29sID0gNSkNCg0KI0NyZWF0ZSB0aGUgNHRoIHNldCBvZiBjb25zdHJhaW50cyBoW2krMV0gKyBNIGJbaV0gPD0gTQ0KY29uc3RyNCA9IG1hdHJpeCgwLCBucm93ID0gNSwgbmNvbCA9IG0pDQpjb2xuYW1lcyhjb25zdHI0KSA9IHZhci5uYW1lcw0KDQpjb25zdHI0WywgMiA6IDZdID0gZGlhZygxLCBucm93ID0gNSwgbmNvbCA9IDUpDQpjb25zdHI0WywgMjAgOiAyNF0gPSBkaWFnKE0sIG5yb3cgPSA1LCBuY29sID0gNSkNCg0KI0JpbmQgQ29uc3RyMSBhbmQgQ29uc3IyIGluIGEgc2luZ2xlIG1hdHJpeA0KY29uc3RyID0gcmJpbmQoY29uc3RyMSwgY29uc3RyMiwgY29uc3RyMywgY29uc3RyNCkNCg0KI0NyZWF0ZSBSSFMgb2YgZWN1YXRpb25zDQojQ3JlYXRlIFJIUyBjb25zcmFpbnRpbiBhIHZlY3RvciBvZiAxIHJvdyB4IDEzIHJvd3MNCnJocyA9IGMocmVwKDAsIDYpLCAyMCwgMzAsIDYwLCA1NSwgNDAsIDQ1LCA1MCwgcmVwKDAsIDUpLCByZXAoTSwgNSkpDQoNCiNDcmVhdGUgZGlyZWN0aW9uDQpjb25zdHIuZGlyIDwtIGMocmVwKCI9IiwgNyksIHJlcCgiPj0iLCA2KSwgcmVwKCc8PScsIDUpLCByZXAoJzw9JywgNSkpDQoNCiNTb2x2ZSBQcm9ibGVtDQpzdGFmZmluZyA9IGxwKCdtaW4nLCBvYmouZnVuLCBjb25zdHIsIGNvbnN0ci5kaXIsIHJocywgYmluYXJ5LnZlYyA9IGMoMjAgOiAyNCkpDQoNCiNTb2x1dGlvbg0Kc3RhZmZpbmcgPSBzdGFmZmluZyRzb2x1dGlvbg0Kc3RhZmZpbmcgPSBkYXRhLmZyYW1lKFZhci5OYW1lID0gdmFyLm5hbWVzLCBTb2x1dGlvbiA9IHN0YWZmaW5nKQ0Kc3RhZmZpbmcNCmBgYA==