highs 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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