github.com/gopherd/gonum@v0.0.4/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  	"github.com/gopherd/gonum/floats"
     9  	"github.com/gopherd/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  //  minimize cᵀ * x
    23  //  s.t      G * x <= h
    24  //           A * x = b
    25  // And the standard form is:
    26  //  minimize cNewᵀ * x
    27  //  s.t      aNew * x = bNew
    28  //           x >= 0
    29  // If there are no constraints of the given type, the inputs may be nil.
    30  func Convert(c []float64, g mat.Matrix, h []float64, a mat.Matrix, b []float64) (cNew []float64, aNew *mat.Dense, bNew []float64) {
    31  	nVar := len(c)
    32  	nIneq := len(h)
    33  
    34  	// Check input sizes.
    35  	if g == nil {
    36  		if nIneq != 0 {
    37  			panic(badShape)
    38  		}
    39  	} else {
    40  		gr, gc := g.Dims()
    41  		if gr != nIneq {
    42  			panic(badShape)
    43  		}
    44  		if gc != nVar {
    45  			panic(badShape)
    46  		}
    47  	}
    48  
    49  	nEq := len(b)
    50  	if a == nil {
    51  		if nEq != 0 {
    52  			panic(badShape)
    53  		}
    54  	} else {
    55  		ar, ac := a.Dims()
    56  		if ar != nEq {
    57  			panic(badShape)
    58  		}
    59  		if ac != nVar {
    60  			panic(badShape)
    61  		}
    62  	}
    63  
    64  	// Convert the general form LP.
    65  	// Derivation:
    66  	// 0. Start with general form
    67  	//  min.	cᵀ * x
    68  	//  s.t.	G * x <= h
    69  	//  		A * x = b
    70  	// 1. Introduce slack variables for each constraint
    71  	//  min. 	cᵀ * x
    72  	//  s.t.	G * x + s = h
    73  	//			A * x = b
    74  	//      	s >= 0
    75  	// 2. Add non-negativity constraints for x by splitting x
    76  	// into positive and negative components.
    77  	//   x = xp - xn
    78  	//   xp >= 0, xn >= 0
    79  	// This makes the LP
    80  	//  min.	cᵀ * xp - cᵀ xn
    81  	//  s.t. 	G * xp - G * xn + s = h
    82  	//			A * xp  - A * xn = b
    83  	//			xp >= 0, xn >= 0, s >= 0
    84  	// 3. Write the above in standard form:
    85  	//  xt = [xp
    86  	//	 	  xn
    87  	//		  s ]
    88  	//  min.	[cᵀ, -cᵀ, 0] xt
    89  	//  s.t.	[G, -G, I] xt = h
    90  	//   		[A, -A, 0] xt = b
    91  	//			x >= 0
    92  
    93  	// In summary:
    94  	// Original LP:
    95  	//  min.	cᵀ * x
    96  	//  s.t.	G * x <= h
    97  	//  		A * x = b
    98  	// Standard Form:
    99  	//  xt = [xp; xn; s]
   100  	//  min.	[cᵀ, -cᵀ, 0] xt
   101  	//  s.t.	[G, -G, I] xt = h
   102  	//   		[A, -A, 0] xt = b
   103  	//			x >= 0
   104  
   105  	// New size of x is [xp, xn, s]
   106  	nNewVar := nVar + nVar + nIneq
   107  
   108  	// Construct cNew = [c; -c; 0]
   109  	cNew = make([]float64, nNewVar)
   110  	copy(cNew, c)
   111  	copy(cNew[nVar:], c)
   112  	floats.Scale(-1, cNew[nVar:2*nVar])
   113  
   114  	// New number of equality constraints is the number of total constraints.
   115  	nNewEq := nIneq + nEq
   116  
   117  	// Construct bNew = [h, b].
   118  	bNew = make([]float64, nNewEq)
   119  	copy(bNew, h)
   120  	copy(bNew[nIneq:], b)
   121  
   122  	// Construct aNew = [G, -G, I; A, -A, 0].
   123  	aNew = mat.NewDense(nNewEq, nNewVar, nil)
   124  	if nIneq != 0 {
   125  		aNew.Slice(0, nIneq, 0, nVar).(*mat.Dense).Copy(g)
   126  		aNew.Slice(0, nIneq, nVar, 2*nVar).(*mat.Dense).Scale(-1, g)
   127  		aView := aNew.Slice(0, nIneq, 2*nVar, 2*nVar+nIneq).(*mat.Dense)
   128  		for i := 0; i < nIneq; i++ {
   129  			aView.Set(i, i, 1)
   130  		}
   131  	}
   132  	if nEq != 0 {
   133  		aNew.Slice(nIneq, nIneq+nEq, 0, nVar).(*mat.Dense).Copy(a)
   134  		aNew.Slice(nIneq, nIneq+nEq, nVar, 2*nVar).(*mat.Dense).Scale(-1, a)
   135  	}
   136  	return cNew, aNew, bNew
   137  }