GALAHAD LPA package#
purpose#
The lpa
package uses the simplex method to solve a
given linear program (LP).
The aim is to minimize the linear objective function
See Section 4 of $GALAHAD/doc/lpa.pdf for a brief description of the method employed and other details.
N.B. The package is simply a sophisticated interface to the HSL package
LA04, and requires that a user has obtained the latter. LA04 is not
included in GALAHAD but is available without charge to recognised
academics, see http://www.hsl.rl.ac.uk/catalogue/la04.html. If LA04 is
unavailable, the interior- point linear programming package lpb
is an alternative.
terminology#
Any required solution \(x\) necessarily satisfies the primal optimality conditions
The so-called dual to this problem is another linear program
method#
The bulk of the work is peformed by the HSL package LA04. The main subbroutine from this package requires that the input problem be transformed into the ``standard form’’
Once a selection of \(m'\) independent (non-basic) variables is made, the enlarged constraints determine the remaining \(m'\) dependent ({basic) variables. The simplex method is a scheme for systematically adjusting the choice of basic and non-basic variables until a set which defines an optimal solution of the standard-form LP is obtained. Each iteration of the simplex method requires the solution of a number of sets of linear equations whose coefficient matrix is the basis matrix \(B\), made up of the columns of \([A' \;\; I]\) corresponding to the basic variables, or its transpose \(B^T\). As the basis matrices for consecutive iterations are closely related, it is normally advantageous to update (rather than recompute) their factorizations as the computation proceeds. If an initial basis is not provided by the user, a set of basic variables which provide a (permuted) triangular basis matrix is found by the simple crash algorithm of Gould and Reid (1989), and initial steepest-edge weights are calculated.
Phases one (finding a feasible solution) and two (solving the standard-form LP) of the simplex method are applied, as appropriate, with the choice of entering variable as described by Goldfarb and Reid (1977) and the choice of leaving variable as proposed by Harris (1973). Refactorizations of the basis matrix are performed whenever doing so will reduce the average iteration time or there is insufficient memory for its factors. The reduced cost for the entering variable is computed afresh. If it is found to be of a different sign from the recurred value or more than 10% different in magnitude, a fresh computation of all the reduced costs is performed. Details of the factorization and updating procedures are given by Reid (1982). Iterative refinement is encouraged for the basic solution and for the reduced costs after each factorization of the basis matrix and when they are recomputed at the end of phase 1.
references#
D. Goldfarb and J. K. Reid, ``A practicable steepest-edge simplex algorithm’’. Mathematical Programming 12 (1977) 361-371.
N. I. M. Gould and J. K. Reid, ``New crash procedures for large systems of linear constraints’’. Mathematical Programming 45 (1989) 475-501.
P. M. J. Harris, ``Pivot selection methods of the Devex LP code’’. Mathematical Programming 5 (1973) 1-28.
J. K. Reid, ``A sparsity-exploiting variant of the Bartels-Golub decomposition for linear-programming bases’’. Mathematical Programming 24 (1982) 55-69.
matrix storage#
The unsymmetric \(m\) by \(n\) matrix \(A\) may be presented and stored in a variety of convenient input formats.
Dense storage format: The matrix \(A\) is stored as a compact dense matrix by rows, that is, the values of the entries of each row in turn are stored in order within an appropriate real one-dimensional array. In this case, component \(n \ast i + j\) of the storage array A_val will hold the value \(A_{ij}\) for \(1 \leq i \leq m\), \(1 \leq j \leq n\). The string A_type = ‘dense’ should be specified.
Dense by columns storage format: The matrix \(A\) is stored as a compact dense matrix by columns, that is, the values of the entries of each column in turn are stored in order within an appropriate real one-dimensional array. In this case, component \(m \ast j + i\) of the storage array A_val will hold the value \(A_{ij}\) for \(1 \leq i \leq m\), \(1 \leq j \leq n\). The string A_type = ‘dense_by_columns’ should be specified.
Sparse co-ordinate storage format: Only the nonzero entries of the matrices are stored. For the \(l\)-th entry, \(1 \leq l \leq ne\), of \(A\), its row index i, column index j and value \(A_{ij}\), \(1 \leq i \leq m\), \(1 \leq j \leq n\), are stored as the \(l\)-th components of the integer arrays A_row and A_col and real array A_val, respectively, while the number of nonzeros is recorded as A_ne = \(ne\). The string A_type = ‘coordinate’should be specified.
Sparse row-wise storage format: Again only the nonzero entries are stored, but this time they are ordered so that those in row i appear directly before those in row i+1. For the i-th row of \(A\) the i-th component of the integer array A_ptr holds the position of the first entry in this row, while A_ptr(m+1) holds the total number of entries plus one. The column indices j, \(1 \leq j \leq n\), and values \(A_{ij}\) of the nonzero entries in the i-th row are stored in components l = A_ptr(i), \(\ldots\), A_ptr(i+1)-1, \(1 \leq i \leq m\), of the integer array A_col, and real array A_val, respectively. For sparse matrices, this scheme almost always requires less storage than its predecessor. The string A_type = ‘sparse_by_rows’ should be specified.
Sparse column-wise storage format: Once again only the nonzero entries are stored, but this time they are ordered so that those in column j appear directly before those in column j+1. For the j-th column of \(A\) the j-th component of the integer array A_ptr holds the position of the first entry in this column, while A_ptr(n+1) holds the total number of entries plus one. The row indices i, \(1 \leq i \leq m\), and values \(A_{ij}\) of the nonzero entries in the j-th columnsare stored in components l = A_ptr(j), \(\ldots\), A_ptr(j+1)-1, \(1 \leq j \leq n\), of the integer array A_row, and real array A_val, respectively. As before, for sparse matrices, this scheme almost always requires less storage than the co-ordinate format. The string A_type = ‘sparse_by_columns’ should be specified.
introduction to function calls#
To solve a given problem, functions from the lpa package must be called in the following order:
To solve a given problem, functions from the lpa package must be called in the following order:
lpa_initialize - provide default control parameters and set up initial data structures
lpa_read_specfile (optional) - override control values by reading replacement values from a file
lpa_import - set up problem data structures and fixed values
lpa_reset_control (optional) - possibly change control parameters if a sequence of problems are being solved
lpa_solve_lp - solve the linear program
lpa_information (optional) - recover information about the solution and solution process
lpa_terminate - deallocate data structures
See the examples section for illustrations of use.
parametric real type T#
Below, the symbol T refers to a parametric real type that may be Float32 (single precision), Float64 (double precision) or, if supported, Float128 (quadruple precision).
callable functions#
function lpa_initialize(T, data, control, status)
Set default control values and initialize private data
Parameters:
data |
holds private internal data |
control |
is a structure containing control information (see lpa_control_type) |
status |
is a scalar variable of type Int32 that gives the exit status from the package. Possible values are (currently):
|
function lpa_read_specfile(T, control, specfile)
Read the content of a specification file, and assign values associated with given keywords to the corresponding control parameters. An in-depth discussion of specification files is available, and a detailed list of keywords with associated default values is provided in $GALAHAD/src/lpa/LPA.template. See also Table 2.1 in the Fortran documentation provided in $GALAHAD/doc/lpa.pdf for a list of how these keywords relate to the components of the control structure.
Parameters:
control |
is a structure containing control information (see lpa_control_type) |
specfile |
is a one-dimensional array of type Vararg{Cchar} that must give the name of the specification file |
function lpa_import(T, control, data, status, n, m, A_type, A_ne, A_row, A_col, A_ptr)
Import problem data into internal storage prior to solution.
Parameters:
control |
is a structure whose members provide control parameters for the remaining procedures (see lpa_control_type) |
data |
holds private internal data |
status |
is a scalar variable of type Int32 that gives the exit status from the package. Possible values are:
|
n |
is a scalar variable of type Int32 that holds the number of variables. |
m |
is a scalar variable of type Int32 that holds the number of general linear constraints. |
A_type |
is a one-dimensional array of type Vararg{Cchar} that specifies the unsymmetric storage scheme used for the constraint Jacobian, \(A\). It should be one of ‘coordinate’, ‘sparse_by_rows’ or ‘dense; lower or upper case variants are allowed. |
A_ne |
is a scalar variable of type Int32 that holds the number of entries in \(A\) in the sparse co-ordinate storage scheme. It need not be set for any of the other schemes. |
A_row |
is a one-dimensional array of size A_ne and type Int32 that holds the row indices of \(A\) in the sparse co-ordinate storage scheme. It need not be set for any of the other schemes, and in this case can be C_NULL. |
A_col |
is a one-dimensional array of size A_ne and type Int32 that holds the column indices of \(A\) in either the sparse co-ordinate, or the sparse row-wise storage scheme. It need not be set when the dense or diagonal storage schemes are used, and in this case can be C_NULL. |
A_ptr |
is a one-dimensional array of size n+1 and type Int32 that holds the starting position of each row of \(A\), as well as the total number of entries, in the sparse row-wise storage scheme. It need not be set when the other schemes are used, and in this case can be C_NULL. |
function lpa_reset_control(T, control, data, status)
Reset control parameters after import if required.
Parameters:
control |
is a structure whose members provide control parameters for the remaining procedures (see lpa_control_type) |
data |
holds private internal data |
status |
is a scalar variable of type Int32 that gives the exit status from the package. Possible values are:
|
function lpa_solve_lp(T, data, status, n, m, g, f, a_ne, A_val, c_l, c_u, x_l, x_u, x, c, y, z, x_stat, c_stat)
Solve the linear program.
Parameters:
data |
holds private internal data |
status |
is a scalar variable of type Int32 that gives the entry and exit status from the package. Possible exit values are:
|
n |
is a scalar variable of type Int32 that holds the number of variables |
m |
is a scalar variable of type Int32 that holds the number of general linear constraints. |
g |
is a one-dimensional array of size n and type T that holds the linear term \(g\) of the objective function. The j-th component of |
f |
is a scalar of type T that holds the constant term \(f\) of the objective function. |
a_ne |
is a scalar variable of type Int32 that holds the number of entries in the constraint Jacobian matrix \(A\). |
A_val |
is a one-dimensional array of size a_ne and type T that holds the values of the entries of the constraint Jacobian matrix \(A\) in any of the available storage schemes. |
c_l |
is a one-dimensional array of size m and type T that holds the lower bounds \(c^l\) on the constraints \(A x\). The i-th component of |
c_u |
is a one-dimensional array of size m and type T that holds the upper bounds \(c^l\) on the constraints \(A x\). The i-th component of |
x_l |
is a one-dimensional array of size n and type T that holds the lower bounds \(x^l\) on the variables \(x\). The j-th component of |
x_u |
is a one-dimensional array of size n and type T that holds the upper bounds \(x^l\) on the variables \(x\). The j-th component of |
x |
is a one-dimensional array of size n and type T that holds the values \(x\) of the optimization variables. The j-th component of |
c |
is a one-dimensional array of size m and type T that holds the residual \(c(x)\). The i-th component of |
y |
is a one-dimensional array of size n and type T that holds the values \(y\) of the Lagrange multipliers for the general linear constraints. The j-th component of |
z |
is a one-dimensional array of size n and type T that holds the values \(z\) of the dual variables. The j-th component of |
x_stat |
is a one-dimensional array of size n and type Int32 that gives the optimal status of the problem variables. If x_stat(j) is negative, the variable \(x_j\) most likely lies on its lower bound, if it is positive, it lies on its upper bound, and if it is zero, it lies between its bounds. |
c_stat |
is a one-dimensional array of size m and type Int32 that gives the optimal status of the general linear constraints. If c_stat(i) is negative, the constraint value \(a_i^Tx\) most likely lies on its lower bound, if it is positive, it lies on its upper bound, and if it is zero, it lies between its bounds. |
function lpa_information(T, data, inform, status)
Provides output information
Parameters:
data |
holds private internal data |
inform |
is a structure containing output information (see lpa_inform_type) |
status |
is a scalar variable of type Int32 that gives the exit status from the package. Possible values are (currently):
|
function lpa_terminate(T, data, control, inform)
Deallocate all internal private storage
Parameters:
data |
holds private internal data |
control |
is a structure containing control information (see lpa_control_type) |
inform |
is a structure containing output information (see lpa_inform_type) |
available structures#
lpa_control_type structure#
struct lpa_control_type{T} f_indexing::Bool error::Int32 out::Int32 print_level::Int32 start_print::Int32 stop_print::Int32 maxit::Int32 max_iterative_refinements::Int32 min_real_factor_size::Int32 min_integer_factor_size::Int32 random_number_seed::Int32 sif_file_device::Int32 qplib_file_device::Int32 infinity::T tol_data::T feas_tol::T relative_pivot_tolerance::T growth_limit::T zero_tolerance::T change_tolerance::T identical_bounds_tol::T cpu_time_limit::T clock_time_limit::T scale::Bool dual::Bool warm_start::Bool steepest_edge::Bool space_critical::Bool deallocate_error_fatal::Bool generate_sif_file::Bool generate_qplib_file::Bool sif_file_name::NTuple{31,Cchar} qplib_file_name::NTuple{31,Cchar} prefix::NTuple{31,Cchar}
detailed documentation#
control derived type as a Julia structure
components#
Bool f_indexing
use C or Fortran sparse matrix indexing
Int32 error
error and warning diagnostics occur on stream error
Int32 out
general output occurs on stream out
Int32 print_level
the level of output required is specified by print_level (>= 2 turns on LA04 output)
Int32 start_print
any printing will start on this iteration
Int32 stop_print
any printing will stop on this iteration
Int32 maxit
at most maxit inner iterations are allowed
Int32 max_iterative_refinements
maximum number of iterative refinements allowed
Int32 min_real_factor_size
initial size for real array for the factors and other data
Int32 min_integer_factor_size
initial size for integer array for the factors and other data
Int32 random_number_seed
the initial seed used when generating random numbers
Int32 sif_file_device
specifies the unit number to write generated SIF file describing the current problem
Int32 qplib_file_device
specifies the unit number to write generated QPLIB file describing the current problem
T infinity
any bound larger than infinity in modulus will be regarded as infinite
T tol_data
the tolerable relative perturbation of the data (A,g,..) defining the problem
T feas_tol
any constraint violated by less than feas_tol will be considered to be satisfied
T relative_pivot_tolerance
pivot threshold used to control the selection of pivot elements in the matrix factorization. Any potential pivot which is less than the largest entry in its row times the threshold is excluded as a candidate
T growth_limit
limit to control growth in the upated basis factors. A refactorization occurs if the growth exceeds this limit
T zero_tolerance
any entry in the basis smaller than this is considered zero
T change_tolerance
any solution component whose change is smaller than a tolerence times the largest change may be considered to be zero
T identical_bounds_tol
any pair of constraint bounds (c_l,c_u) or (x_l,x_u) that are closer than identical_bounds_tol will be reset to the average of their values
T cpu_time_limit
the maximum CPU time allowed (-ve means infinite)
T clock_time_limit
the maximum elapsed clock time allowed (-ve means infinite)
Bool scale
if .scale is true, the problem will be automatically scaled prior to solution. This may improve computation time and accuracy
Bool dual
should the dual problem be solved rather than the primal?
Bool warm_start
should a warm start using the data in C_stat and X_stat be attempted?
Bool steepest_edge
should steepest-edge weights be used to detetrmine the variable leaving the basis?
Bool space_critical
if .space_critical is true, every effort will be made to use as little space as possible. This may result in longer computation time
Bool deallocate_error_fatal
if .deallocate_error_fatal is true, any array/pointer deallocation error will terminate execution. Otherwise, computation will continue
Bool generate_sif_file
if .generate_sif_file is .true. if a SIF file describing the current problem is to be generated
Bool generate_qplib_file
if .generate_qplib_file is .true. if a QPLIB file describing the current problem is to be generated
NTuple{31,Cchar} sif_file_name
name of generated SIF file containing input problem
NTuple{31,Cchar} qplib_file_name
name of generated QPLIB file containing input problem
NTuple{31,Cchar} prefix
all output lines will be prefixed by .prefix(2:LEN(TRIM(.prefix))-1) where .prefix contains the required string enclosed in quotes, e.g. “string” or ‘string’
lpa_time_type structure#
struct lpa_time_type{T} total::T preprocess::T clock_total::T clock_preprocess::T
detailed documentation#
time derived type as a Julia structure
components#
T total
the total CPU time spent in the package
T preprocess
the CPU time spent preprocessing the problem
T clock_total
the total clock time spent in the package
T clock_preprocess
the clock time spent preprocessing the problem
lpa_inform_type structure#
struct lpa_inform_type{T} status::Int32 alloc_status::Int32 bad_alloc::NTuple{81,Cchar} iter::Int32 la04_job::Int32 la04_job_info::Int32 obj::T primal_infeasibility::T feasible::Bool RINFO::NTuple{40,T} time::lpa_time_type{T} rpd_inform::rpd_inform_type
detailed documentation#
inform derived type as a Julia structure
components#
Int32 status
return status. See LPA_solve for details
Int32 alloc_status
the status of the last attempted allocation/deallocation
NTuple{81,Cchar} bad_alloc
the name of the array for which an allocation/deallocation error occurred
Int32 iter
the total number of iterations required
Int32 la04_job
the final value of la04’s job argument
Int32 la04_job_info
any extra information from an unsuccessfull call to LA04 (LA04’s RINFO(35)
T obj
the value of the objective function at the best estimate of the solution determined by LPA_solve
T primal_infeasibility
the value of the primal infeasibility
Bool feasible
is the returned “solution” feasible?
T RINFO[40]
the information array from LA04
struct lpa_time_type time
timings (see above)
struct rpd_inform_type rpd_inform
inform parameters for RPD
example calls#
This is an example of how to use the package to solve a linear program; the code is available in $GALAHAD/src/lpa/Julia/test_lpa.jl . A variety of supported constraint matrix storage formats are shown.
# test_lpa.jl
# Simple code to test the Julia interface to LPA
using GALAHAD
using Test
using Printf
using Accessors
using Quadmath
function test_lpa(::Type{T}) where T
# Derived types
data = Ref{Ptr{Cvoid}}()
control = Ref{lpa_control_type{T}}()
inform = Ref{lpa_inform_type{T}}()
# Set problem data
n = 3 # dimension
m = 2 # number of general constraints
g = T[0.0, 2.0, 0.0] # linear term in the objective
f = one(T) # constant term in the objective
A_ne = 4 # Jacobian elements
A_row = Cint[1, 1, 2, 2] # row indices
A_col = Cint[1, 2, 2, 3] # column indices
A_ptr = Cint[1, 3, 5] # row pointers
A_val = T[2.0, 1.0, 1.0, 1.0] # values
c_l = T[1.0, 2.0] # constraint lower bound
c_u = T[2.0, 2.0] # constraint upper bound
x_l = T[-1.0, -Inf, -Inf] # variable lower bound
x_u = T[1.0, Inf, 2.0] # variable upper bound
# Set output storage
c = zeros(T, m) # constraint values
x_stat = zeros(Cint, n) # variable status
c_stat = zeros(Cint, m) # constraint status
st = ' '
status = Ref{Cint}()
@printf(" Fortran sparse matrix indexing\n\n")
@printf(" basic tests of lp storage formats\n\n")
for d in 1:3
# Initialize LPA
lpa_initialize(T, data, control, status)
# Set user-defined control options
@reset control[].f_indexing = true # Fortran sparse matrix indexing
# Start from 0
x = T[0.0, 0.0, 0.0]
y = T[0.0, 0.0]
z = T[0.0, 0.0, 0.0]
# sparse co-ordinate storage
if d == 1
st = 'C'
lpa_import(T, control, data, status, n, m,
"coordinate", A_ne, A_row, A_col, C_NULL)
lpa_solve_lp(T, data, status, n, m, g, f,
A_ne, A_val, c_l, c_u, x_l, x_u, x, c, y, z,
x_stat, c_stat)
end
# sparse by rows
if d == 2
st = 'R'
lpa_import(T, control, data, status, n, m,
"sparse_by_rows", A_ne, C_NULL, A_col, A_ptr)
lpa_solve_lp(T, data, status, n, m, g, f,
A_ne, A_val, c_l, c_u, x_l, x_u, x, c, y, z,
x_stat, c_stat)
end
# dense
if d == 3
st = 'D'
A_dense_ne = 6 # number of elements of A
A_dense = T[2.0, 1.0, 0.0, 0.0, 1.0, 1.0]
lpa_import(T, control, data, status, n, m,
"dense", A_ne, C_NULL, C_NULL, C_NULL)
lpa_solve_lp(T, data, status, n, m, g, f,
A_dense_ne, A_dense, c_l, c_u, x_l, x_u,
x, c, y, z, x_stat, c_stat)
end
lpa_information(T, data, inform, status)
if inform[].status == 0
@printf("%c:%6i iterations. Optimal objective value = %5.2f status = %1i\n", st,
inform[].iter, inform[].obj, inform[].status)
else
@printf("%c: LPA_solve exit status = %1i\n", st, inform[].status)
end
# @printf("x: ")
# for i = 1:n
# @printf("%f ", x[i])
# end
# @printf("\n")
# @printf("gradient: ")
# for i = 1:n
# @printf("%f ", g[i])
# end
# @printf("\n")
# Delete internal workspace
lpa_terminate(T, data, control, inform)
end
return 0
end
@testset "LPA" begin
@test test_lpa(Float32) == 0
@test test_lpa(Float64) == 0
@test test_lpa(Float128) == 0
end