gonum.org/v1/gonum@v0.14.0/optimize/convex/lp/convert.go (about)

     1  // Copyright ©2016 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package lp
     6  
     7  import (
     8  	"gonum.org/v1/gonum/floats"
     9  	"gonum.org/v1/gonum/mat"
    10  )
    11  
    12  // TODO(btracey): Have some sort of preprocessing step for helping to fix A to make it
    13  // full rank?
    14  // TODO(btracey): Reduce rows? Get rid of all zeros, places where only one variable
    15  // is there, etc. Could be implemented with a Reduce function.
    16  // TODO(btracey): Provide method of artificial variables for help when problem
    17  // is infeasible?
    18  // TODO(btracey): Add an lp.Solve that solves an LP in non-standard form.
    19  
    20  // Convert converts a General-form LP into a standard form LP.
    21  // The general form of an LP is:
    22  //
    23  //	minimize cᵀ * x
    24  //	s.t      G * x <= h
    25  //	         A * x = b
    26  //
    27  // And the standard form is:
    28  //
    29  //	minimize cNewᵀ * x
    30  //	s.t      aNew * x = bNew
    31  //	         x >= 0
    32  //
    33  // If there are no constraints of the given type, the inputs may be nil.
    34  func Convert(c []float64, g mat.Matrix, h []float64, a mat.Matrix, b []float64) (cNew []float64, aNew *mat.Dense, bNew []float64) {
    35  	nVar := len(c)
    36  	nIneq := len(h)
    37  
    38  	// Check input sizes.
    39  	if g == nil {
    40  		if nIneq != 0 {
    41  			panic(badShape)
    42  		}
    43  	} else {
    44  		gr, gc := g.Dims()
    45  		if gr != nIneq {
    46  			panic(badShape)
    47  		}
    48  		if gc != nVar {
    49  			panic(badShape)
    50  		}
    51  	}
    52  
    53  	nEq := len(b)
    54  	if a == nil {
    55  		if nEq != 0 {
    56  			panic(badShape)
    57  		}
    58  	} else {
    59  		ar, ac := a.Dims()
    60  		if ar != nEq {
    61  			panic(badShape)
    62  		}
    63  		if ac != nVar {
    64  			panic(badShape)
    65  		}
    66  	}
    67  
    68  	// Convert the general form LP.
    69  	// Derivation:
    70  	// 0. Start with general form
    71  	//  min.	cᵀ * x
    72  	//  s.t.	G * x <= h
    73  	//  		A * x = b
    74  	// 1. Introduce slack variables for each constraint
    75  	//  min. 	cᵀ * x
    76  	//  s.t.	G * x + s = h
    77  	//			A * x = b
    78  	//      	s >= 0
    79  	// 2. Add non-negativity constraints for x by splitting x
    80  	// into positive and negative components.
    81  	//   x = xp - xn
    82  	//   xp >= 0, xn >= 0
    83  	// This makes the LP
    84  	//  min.	cᵀ * xp - cᵀ xn
    85  	//  s.t. 	G * xp - G * xn + s = h
    86  	//			A * xp  - A * xn = b
    87  	//			xp >= 0, xn >= 0, s >= 0
    88  	// 3. Write the above in standard form:
    89  	//  xt = [xp
    90  	//	 	  xn
    91  	//		  s ]
    92  	//  min.	[cᵀ, -cᵀ, 0] xt
    93  	//  s.t.	[G, -G, I] xt = h
    94  	//   		[A, -A, 0] xt = b
    95  	//			x >= 0
    96  
    97  	// In summary:
    98  	// Original LP:
    99  	//  min.	cᵀ * x
   100  	//  s.t.	G * x <= h
   101  	//  		A * x = b
   102  	// Standard Form:
   103  	//  xt = [xp; xn; s]
   104  	//  min.	[cᵀ, -cᵀ, 0] xt
   105  	//  s.t.	[G, -G, I] xt = h
   106  	//   		[A, -A, 0] xt = b
   107  	//			x >= 0
   108  
   109  	// New size of x is [xp, xn, s]
   110  	nNewVar := nVar + nVar + nIneq
   111  
   112  	// Construct cNew = [c; -c; 0]
   113  	cNew = make([]float64, nNewVar)
   114  	copy(cNew, c)
   115  	copy(cNew[nVar:], c)
   116  	floats.Scale(-1, cNew[nVar:2*nVar])
   117  
   118  	// New number of equality constraints is the number of total constraints.
   119  	nNewEq := nIneq + nEq
   120  
   121  	// Construct bNew = [h, b].
   122  	bNew = make([]float64, nNewEq)
   123  	copy(bNew, h)
   124  	copy(bNew[nIneq:], b)
   125  
   126  	// Construct aNew = [G, -G, I; A, -A, 0].
   127  	aNew = mat.NewDense(nNewEq, nNewVar, nil)
   128  	if nIneq != 0 {
   129  		aNew.Slice(0, nIneq, 0, nVar).(*mat.Dense).Copy(g)
   130  		aNew.Slice(0, nIneq, nVar, 2*nVar).(*mat.Dense).Scale(-1, g)
   131  		aView := aNew.Slice(0, nIneq, 2*nVar, 2*nVar+nIneq).(*mat.Dense)
   132  		for i := 0; i < nIneq; i++ {
   133  			aView.Set(i, i, 1)
   134  		}
   135  	}
   136  	if nEq != 0 {
   137  		aNew.Slice(nIneq, nIneq+nEq, 0, nVar).(*mat.Dense).Copy(a)
   138  		aNew.Slice(nIneq, nIneq+nEq, nVar, 2*nVar).(*mat.Dense).Scale(-1, a)
   139  	}
   140  	return cNew, aNew, bNew
   141  }