highs 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 39ebf3fc768ecb688ec539a334c754f573c5f3f070b184f16a946602f12ad3ab
4
- data.tar.gz: 856643d58ea6598e0770fdf43d31f1edd01b5715d695774617d933b08ac25182
3
+ metadata.gz: a6e29b694c68a7f26a7674c5dad300e17b12c5ce3d2a961f33bec5a4663ff1c4
4
+ data.tar.gz: 6f9944e2ceed6395a75372e9fecb5b7d69e42133476eeaef1150146ec28f6532
5
5
  SHA512:
6
- metadata.gz: 75b9b9df7e57ac0a0cd64368bbe68377b6968eb095762c3cd5b645836e162488b38021fe1cb6bd4099f4f41433d6006f67c4bfc847d979f69cb233f494a4ab30
7
- data.tar.gz: 6efca39cba247de891c5aed30e4d9ca6dc1a0a5c1190b357271d96cf16cbaf8d44873fcd4a8a1c1886245c9cdfea8081bf1a372c3ab32f10a68e000b901fc42f
6
+ metadata.gz: 2acd4b5fb27c2a6834cb2412d7b0a9b5c844a33957f6a45d7f776a6f8b89c5a3b8e160e5886de74445c3ca6b8e349fe33f6072a88130338956f0e3583eb2b937
7
+ data.tar.gz: 721cbdcbbda13229ce1ae8393acd55a76bea821899119cd4c83126db19f727a8ad0005362b56efcd195b5f53a3d5d4d19662c18325a4d131b8302244ae45ca25
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.1.1 (2022-04-10)
2
+
3
+ - Added objective value
4
+ - Added support for reading and writing models
5
+
1
6
  ## 0.1.0 (2022-04-05)
2
7
 
3
8
  - First release
data/README.md CHANGED
@@ -16,60 +16,81 @@ gem "highs"
16
16
 
17
17
  *The API is fairly low-level at the moment*
18
18
 
19
- Solve a linear program
19
+ Load a linear program
20
20
 
21
21
  ```ruby
22
- Highs.lp_call(
23
- sense: :minimize,
24
- col_cost: [8, 10],
25
- col_lower: [0, 0],
26
- col_upper: [1e30, 1e30],
27
- row_lower: [7, 12, 6],
28
- row_upper: [1e30, 1e30, 1e30],
29
- a_format: :colwise,
30
- a_start: [0, 3],
31
- a_index: [0, 1, 2, 0, 1, 2],
32
- a_value: [2, 3, 2, 2, 4, 1]
33
- )
22
+ model =
23
+ Highs.lp(
24
+ sense: :minimize,
25
+ col_cost: [8, 10],
26
+ col_lower: [0, 0],
27
+ col_upper: [1e30, 1e30],
28
+ row_lower: [7, 12, 6],
29
+ row_upper: [1e30, 1e30, 1e30],
30
+ a_format: :colwise,
31
+ a_start: [0, 3],
32
+ a_index: [0, 1, 2, 0, 1, 2],
33
+ a_value: [2, 3, 2, 2, 4, 1]
34
+ )
34
35
  ```
35
36
 
36
- Solve a mixed-integer program
37
+ Load a mixed-integer program
37
38
 
38
39
  ```ruby
39
- Highs.mip_call(
40
- sense: :minimize,
41
- col_cost: [8, 10],
42
- col_lower: [0, 0],
43
- col_upper: [1e30, 1e30],
44
- row_lower: [7, 12, 6],
45
- row_upper: [1e30, 1e30, 1e30],
46
- a_format: :colwise,
47
- a_start: [0, 3],
48
- a_index: [0, 1, 2, 0, 1, 2],
49
- a_value: [2, 3, 2, 2, 4, 1],
50
- integrality: [1, 1]
51
- )
40
+ model =
41
+ Highs.mip(
42
+ sense: :minimize,
43
+ col_cost: [8, 10],
44
+ col_lower: [0, 0],
45
+ col_upper: [1e30, 1e30],
46
+ row_lower: [7, 12, 6],
47
+ row_upper: [1e30, 1e30, 1e30],
48
+ a_format: :colwise,
49
+ a_start: [0, 3],
50
+ a_index: [0, 1, 2, 0, 1, 2],
51
+ a_value: [2, 3, 2, 2, 4, 1],
52
+ integrality: [1, 1]
53
+ )
52
54
  ```
53
55
 
54
- Solve a quadratic program
56
+ Load a quadratic program
55
57
 
56
58
  ```ruby
57
- Highs.qp_call(
58
- sense: :minimize,
59
- col_cost: [0, -1, 0],
60
- col_lower: [0, 0, 0],
61
- col_upper: [1e30, 1e30, 1e30],
62
- row_lower: [1, -1e30],
63
- row_upper: [1e30, 1e30],
64
- a_format: :colwise,
65
- a_start: [0, 1, 2],
66
- a_index: [0, 0, 0],
67
- a_value: [1, 1, 1],
68
- q_format: :colwise,
69
- q_start: [0, 2, 3],
70
- q_index: [0, 2, 1, 0, 2],
71
- q_value: [2, -1, 0.2, -1, 2]
72
- )
59
+ model =
60
+ Highs.qp(
61
+ sense: :minimize,
62
+ col_cost: [0, -1, 0],
63
+ col_lower: [0, 0, 0],
64
+ col_upper: [1e30, 1e30, 1e30],
65
+ row_lower: [1, -1e30],
66
+ row_upper: [1e30, 1e30],
67
+ a_format: :colwise,
68
+ a_start: [0, 1, 2],
69
+ a_index: [0, 0, 0],
70
+ a_value: [1, 1, 1],
71
+ q_format: :colwise,
72
+ q_start: [0, 2, 3],
73
+ q_index: [0, 2, 1, 0, 2],
74
+ q_value: [2, -1, 0.2, -1, 2]
75
+ )
76
+ ```
77
+
78
+ Solve
79
+
80
+ ```ruby
81
+ model.solve
82
+ ```
83
+
84
+ Write the program to an MPS file
85
+
86
+ ```ruby
87
+ model.write("model.mps")
88
+ ```
89
+
90
+ Read a program from an MPS file
91
+
92
+ ```ruby
93
+ model = Highs.read("model.mps")
73
94
  ```
74
95
 
75
96
  ## History
data/lib/highs/ffi.rb CHANGED
@@ -36,5 +36,27 @@ module Highs
36
36
  extern "HighsInt Highs_lpCall(HighsInt num_col, HighsInt num_row, HighsInt num_nz, HighsInt a_format, HighsInt sense, double offset, double* col_cost, double* col_lower, double* col_upper, double* row_lower, double* row_upper, HighsInt* a_start, HighsInt* a_index, double* a_value, double* col_value, double* col_dual, double* row_value, double* row_dual, HighsInt* col_basis_status, HighsInt* row_basis_status, HighsInt* model_status)"
37
37
  extern "HighsInt Highs_mipCall(HighsInt num_col, HighsInt num_row, HighsInt num_nz, HighsInt a_format, HighsInt sense, double offset, double* col_cost, double* col_lower, double* col_upper, double* row_lower, double* row_upper, HighsInt* a_start, HighsInt* a_index, double* a_value, HighsInt* integrality, double* col_value, double* row_value, HighsInt* model_status)"
38
38
  extern "HighsInt Highs_qpCall(HighsInt num_col, HighsInt num_row, HighsInt num_nz, HighsInt q_num_nz, HighsInt a_format, HighsInt q_format, HighsInt sense, double offset, double* col_cost, double* col_lower, double* col_upper, double* row_lower, double* row_upper, HighsInt* a_start, HighsInt* a_index, double* a_value, HighsInt* q_start, HighsInt* q_index, double* q_value, double* col_value, double* col_dual, double* row_value, double* row_dual, HighsInt* col_basis_status, HighsInt* row_basis_status, HighsInt* model_status)"
39
+
40
+ extern "void* Highs_create(void)"
41
+ extern "void Highs_destroy(void* highs)"
42
+ extern "HighsInt Highs_readModel(void* highs, char* filename)"
43
+ extern "HighsInt Highs_writeModel(void* highs, char* filename)"
44
+ extern "HighsInt Highs_clearModel(void* highs)"
45
+ extern "HighsInt Highs_run(void* highs)"
46
+ extern "HighsInt Highs_writeSolution(void* highs, char* filename)"
47
+ extern "HighsInt Highs_writeSolutionPretty(void* highs, char* filename)"
48
+ extern "HighsInt Highs_passLp(void* highs, HighsInt num_col, HighsInt num_row, HighsInt num_nz, HighsInt a_format, HighsInt sense, double offset, double* col_cost, double* col_lower, double* col_upper, double* row_lower, double* row_upper, HighsInt* a_start, HighsInt* a_index, double* a_value)"
49
+ extern "HighsInt Highs_passMip(void* highs, HighsInt num_col, HighsInt num_row, HighsInt num_nz, HighsInt a_format, HighsInt sense, double offset, double* col_cost, double* col_lower, double* col_upper, double* row_lower, double* row_upper, HighsInt* a_start, HighsInt* a_index, double* a_value, HighsInt* integrality)"
50
+ extern "HighsInt Highs_passModel(void* highs, HighsInt num_col, HighsInt num_row, HighsInt num_nz, HighsInt q_num_nz, HighsInt a_format, HighsInt q_format, HighsInt sense, double offset, double* col_cost, double* col_lower, double* col_upper, double* row_lower, double* row_upper, HighsInt* a_start, HighsInt* a_index, double* a_value, HighsInt* q_start, HighsInt* q_index, double* q_value, HighsInt* integrality)"
51
+ extern "HighsInt Highs_passHessian(void* highs, HighsInt dim, HighsInt num_nz, HighsInt format, HighsInt* start, HighsInt* index, double* value)"
52
+ extern "HighsInt Highs_setBoolOptionValue(void* highs, char* option, HighsInt value)"
53
+ extern "HighsInt Highs_getSolution(void* highs, double* col_value, double* col_dual, double* row_value, double* row_dual)"
54
+ extern "HighsInt Highs_getBasis(void* highs, HighsInt* col_status, HighsInt* row_status)"
55
+ extern "HighsInt Highs_getModelStatus(void* highs)"
56
+ extern "HighsInt Highs_getDualRay(void* highs, HighsInt* has_dual_ray, double* dual_ray_value)"
57
+ extern "HighsInt Highs_getPrimalRay(void* highs, HighsInt* has_primal_ray, double* primal_ray_value)"
58
+ extern "double Highs_getObjectiveValue(void* highs)"
59
+ extern "HighsInt Highs_getNumCol(void* highs)"
60
+ extern "HighsInt Highs_getNumRow(void* highs)"
39
61
  end
40
62
  end
data/lib/highs/methods.rb CHANGED
@@ -1,76 +1,49 @@
1
1
  module Highs
2
2
  module Methods
3
- def lp_call(sense:, offset: 0, col_cost:, col_lower:, col_upper:, row_lower:, row_upper:, a_format:, a_start:, a_index:, a_value:)
3
+ def lp(sense:, offset: 0, col_cost:, col_lower:, col_upper:, row_lower:, row_upper:, a_format:, a_start:, a_index:, a_value:)
4
4
  num_col = col_cost.size
5
5
  num_row = row_lower.size
6
6
  num_nz = a_index.size
7
7
  a_format = FFI::MATRIX_FORMAT.fetch(a_format)
8
8
  sense = FFI::OBJ_SENSE.fetch(sense)
9
9
 
10
- a_start_size = a_start.size
11
-
12
- col_value = DoubleArray.new(num_col)
13
- col_dual = DoubleArray.new(num_col)
14
- row_value = DoubleArray.new(num_row)
15
- row_dual = DoubleArray.new(num_row)
16
- col_basis = IntArray.new(num_col)
17
- row_basis = IntArray.new(num_row)
18
- model_status = IntArray.new(1)
19
-
20
- check_status FFI.Highs_lpCall(
21
- num_col, num_row, num_nz, a_format, sense, offset,
10
+ model = Model.new
11
+ check_status FFI.Highs_passLp(
12
+ model, num_col, num_row, num_nz, a_format, sense, offset,
22
13
  DoubleArray.new(num_col, col_cost), DoubleArray.new(num_col, col_lower), DoubleArray.new(num_col, col_upper),
23
14
  DoubleArray.new(num_row, row_lower), DoubleArray.new(num_row, row_upper),
24
- IntArray.new(a_start_size, a_start), IntArray.new(num_nz, a_index), DoubleArray.new(num_nz, a_value),
25
- col_value, col_dual,
26
- row_value, row_dual,
27
- col_basis, row_basis,
28
- model_status
15
+ IntArray.new(a_start.size, a_start), IntArray.new(num_nz, a_index), DoubleArray.new(num_nz, a_value),
29
16
  )
17
+ model
18
+ end
30
19
 
31
- {
32
- status: FFI::MODEL_STATUS[model_status.to_a.first],
33
- col_value: col_value.to_a,
34
- col_dual: col_dual.to_a,
35
- row_value: row_value.to_a,
36
- row_dual: row_dual.to_a,
37
- col_basis: col_basis.to_a.map { |v| FFI::BASIS_STATUS[v] },
38
- row_basis: row_basis.to_a.map { |v| FFI::BASIS_STATUS[v] }
39
- }
20
+ def lp_call(**options)
21
+ lp(**options).solve
40
22
  end
41
23
 
42
- def mip_call(sense:, offset: 0, col_cost:, col_lower:, col_upper:, row_lower:, row_upper:, a_format:, a_start:, a_index:, a_value:, integrality:)
24
+ def mip(sense:, offset: 0, col_cost:, col_lower:, col_upper:, row_lower:, row_upper:, a_format:, a_start:, a_index:, a_value:, integrality:)
43
25
  num_col = col_cost.size
44
26
  num_row = row_lower.size
45
27
  num_nz = a_index.size
46
28
  a_format = FFI::MATRIX_FORMAT.fetch(a_format)
47
29
  sense = FFI::OBJ_SENSE.fetch(sense)
48
30
 
49
- a_start_size = a_start.size
50
-
51
- col_value = DoubleArray.new(num_col)
52
- row_value = DoubleArray.new(num_row)
53
- model_status = IntArray.new(1)
54
-
55
- check_status FFI.Highs_mipCall(
56
- num_col, num_row, num_nz, a_format, sense, offset,
31
+ model = Model.new
32
+ check_status FFI.Highs_passMip(
33
+ model, num_col, num_row, num_nz, a_format, sense, offset,
57
34
  DoubleArray.new(num_col, col_cost), DoubleArray.new(num_col, col_lower), DoubleArray.new(num_col, col_upper),
58
35
  DoubleArray.new(num_row, row_lower), DoubleArray.new(num_row, row_upper),
59
- IntArray.new(a_start_size, a_start), IntArray.new(num_nz, a_index), DoubleArray.new(num_nz, a_value),
60
- IntArray.new(num_col, integrality),
61
- col_value,
62
- row_value,
63
- model_status
36
+ IntArray.new(a_start.size, a_start), IntArray.new(num_nz, a_index), DoubleArray.new(num_nz, a_value),
37
+ IntArray.new(num_col, integrality)
64
38
  )
39
+ model
40
+ end
65
41
 
66
- {
67
- status: FFI::MODEL_STATUS[model_status.to_a.first],
68
- col_value: col_value.to_a,
69
- row_value: row_value.to_a
70
- }
42
+ def mip_call(**options)
43
+ mip(**options).solve.slice(:status, :obj_value, :col_value, :row_value)
71
44
  end
72
45
 
73
- def qp_call(sense:, offset: 0, col_cost:, col_lower:, col_upper:, row_lower:, row_upper:, a_format:, a_start:, a_index:, a_value:, q_format:, q_start:, q_index:, q_value:)
46
+ def qp(sense:, offset: 0, col_cost:, col_lower:, col_upper:, row_lower:, row_upper:, a_format:, a_start:, a_index:, a_value:, q_format:, q_start:, q_index:, q_value:)
74
47
  num_col = col_cost.size
75
48
  num_row = row_lower.size
76
49
  num_nz = a_index.size
@@ -79,38 +52,25 @@ module Highs
79
52
  q_format = FFI::MATRIX_FORMAT.fetch(q_format)
80
53
  sense = FFI::OBJ_SENSE.fetch(sense)
81
54
 
82
- a_start_size = a_start.size
83
- q_start_size = q_start.size
84
-
85
- col_value = DoubleArray.new(num_col)
86
- col_dual = DoubleArray.new(num_col)
87
- row_value = DoubleArray.new(num_row)
88
- row_dual = DoubleArray.new(num_row)
89
- col_basis = IntArray.new(num_col)
90
- row_basis = IntArray.new(num_row)
91
- model_status = IntArray.new(1)
92
-
93
- check_status FFI.Highs_qpCall(
94
- num_col, num_row, num_nz, q_num_nz, a_format, q_format, sense, offset,
55
+ model = Model.new
56
+ check_status FFI.Highs_passModel(
57
+ model, num_col, num_row, num_nz, q_num_nz, a_format, q_format, sense, offset,
95
58
  DoubleArray.new(num_col, col_cost), DoubleArray.new(num_col, col_lower), DoubleArray.new(num_col, col_upper),
96
59
  DoubleArray.new(num_row, row_lower), DoubleArray.new(num_row, row_upper),
97
- IntArray.new(a_start_size, a_start), IntArray.new(num_nz, a_index), DoubleArray.new(num_nz, a_value),
98
- IntArray.new(q_start_size, q_start), IntArray.new(q_num_nz, q_index), DoubleArray.new(q_num_nz, q_value),
99
- col_value, col_dual,
100
- row_value, row_dual,
101
- col_basis, row_basis,
102
- model_status
60
+ IntArray.new(a_start.size, a_start), IntArray.new(num_nz, a_index), DoubleArray.new(num_nz, a_value),
61
+ IntArray.new(q_start.size, q_start), IntArray.new(q_num_nz, q_index), DoubleArray.new(q_num_nz, q_value), nil
103
62
  )
63
+ model
64
+ end
65
+
66
+ def qp_call(**options)
67
+ qp(**options).solve
68
+ end
104
69
 
105
- {
106
- status: FFI::MODEL_STATUS[model_status.to_a.first],
107
- col_value: col_value.to_a,
108
- col_dual: col_dual.to_a,
109
- row_value: row_value.to_a,
110
- row_dual: row_dual.to_a,
111
- col_basis: col_basis.to_a.map { |v| FFI::BASIS_STATUS[v] },
112
- row_basis: row_basis.to_a.map { |v| FFI::BASIS_STATUS[v] }
113
- }
70
+ def read(filename)
71
+ model = Model.new
72
+ check_status FFI.Highs_readModel(model, filename)
73
+ model
114
74
  end
115
75
 
116
76
  private
@@ -0,0 +1,63 @@
1
+ module Highs
2
+ class Model
3
+ def initialize
4
+ @ptr = FFI.Highs_create
5
+ ObjectSpace.define_finalizer(self, self.class.finalize(@ptr))
6
+
7
+ # TODO add option
8
+ check_status FFI.Highs_setBoolOptionValue(@ptr, "output_flag", 0)
9
+ end
10
+
11
+ def solve
12
+ col_value = DoubleArray.new(num_col)
13
+ col_dual = DoubleArray.new(num_col)
14
+ row_value = DoubleArray.new(num_row)
15
+ row_dual = DoubleArray.new(num_row)
16
+ col_basis = IntArray.new(num_col)
17
+ row_basis = IntArray.new(num_row)
18
+
19
+ check_status FFI.Highs_run(@ptr)
20
+ check_status FFI.Highs_getSolution(@ptr, col_value, col_dual, row_value, row_dual)
21
+ check_status FFI.Highs_getBasis(@ptr, col_basis, row_basis)
22
+ model_status = FFI.Highs_getModelStatus(@ptr)
23
+
24
+ {
25
+ status: FFI::MODEL_STATUS[model_status],
26
+ obj_value: FFI.Highs_getObjectiveValue(@ptr),
27
+ col_value: col_value.to_a,
28
+ col_dual: col_dual.to_a,
29
+ row_value: row_value.to_a,
30
+ row_dual: row_dual.to_a,
31
+ col_basis: col_basis.to_a.map { |v| FFI::BASIS_STATUS[v] },
32
+ row_basis: row_basis.to_a.map { |v| FFI::BASIS_STATUS[v] }
33
+ }
34
+ end
35
+
36
+ def write(filename)
37
+ check_status FFI.Highs_writeModel(@ptr, filename)
38
+ end
39
+
40
+ def to_ptr
41
+ @ptr
42
+ end
43
+
44
+ def self.finalize(ptr)
45
+ # must use proc instead of stabby lambda
46
+ proc { FFI.Highs_destroy(ptr) }
47
+ end
48
+
49
+ private
50
+
51
+ def num_col
52
+ FFI.Highs_getNumCol(@ptr)
53
+ end
54
+
55
+ def num_row
56
+ FFI.Highs_getNumRow(@ptr)
57
+ end
58
+
59
+ def check_status(status)
60
+ Highs.send(:check_status, status)
61
+ end
62
+ end
63
+ end
data/lib/highs/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Highs
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
data/lib/highs.rb CHANGED
@@ -4,6 +4,7 @@ require "fiddle/import"
4
4
  # modules
5
5
  require "highs/array"
6
6
  require "highs/methods"
7
+ require "highs/model"
7
8
  require "highs/version"
8
9
 
9
10
  module Highs
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: highs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-05 00:00:00.000000000 Z
11
+ date: 2022-04-11 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: andrew@ankane.org
@@ -23,6 +23,7 @@ files:
23
23
  - lib/highs/array.rb
24
24
  - lib/highs/ffi.rb
25
25
  - lib/highs/methods.rb
26
+ - lib/highs/model.rb
26
27
  - lib/highs/version.rb
27
28
  - vendor/LICENSE
28
29
  - vendor/libhighs.arm64.dylib
@@ -48,7 +49,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
48
49
  - !ruby/object:Gem::Version
49
50
  version: '0'
50
51
  requirements: []
51
- rubygems_version: 3.1.6
52
+ rubygems_version: 3.3.7
52
53
  signing_key:
53
54
  specification_version: 4
54
55
  summary: Linear optimization for Ruby