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 +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +65 -44
- data/lib/highs/ffi.rb +22 -0
- data/lib/highs/methods.rb +35 -75
- data/lib/highs/model.rb +63 -0
- data/lib/highs/version.rb +1 -1
- data/lib/highs.rb +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6e29b694c68a7f26a7674c5dad300e17b12c5ce3d2a961f33bec5a4663ff1c4
|
4
|
+
data.tar.gz: 6f9944e2ceed6395a75372e9fecb5b7d69e42133476eeaef1150146ec28f6532
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2acd4b5fb27c2a6834cb2412d7b0a9b5c844a33957f6a45d7f776a6f8b89c5a3b8e160e5886de74445c3ca6b8e349fe33f6072a88130338956f0e3583eb2b937
|
7
|
+
data.tar.gz: 721cbdcbbda13229ce1ae8393acd55a76bea821899119cd4c83126db19f727a8ad0005362b56efcd195b5f53a3d5d4d19662c18325a4d131b8302244ae45ca25
|
data/CHANGELOG.md
CHANGED
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
|
-
|
19
|
+
Load a linear program
|
20
20
|
|
21
21
|
```ruby
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
37
|
+
Load a mixed-integer program
|
37
38
|
|
38
39
|
```ruby
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
56
|
+
Load a quadratic program
|
55
57
|
|
56
58
|
```ruby
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
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
|
-
|
11
|
-
|
12
|
-
|
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(
|
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
|
-
|
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
|
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
|
-
|
50
|
-
|
51
|
-
|
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(
|
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
|
-
|
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
|
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
|
-
|
83
|
-
|
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(
|
98
|
-
IntArray.new(
|
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
|
-
|
107
|
-
|
108
|
-
|
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
|
data/lib/highs/model.rb
ADDED
@@ -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
data/lib/highs.rb
CHANGED
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.
|
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-
|
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.
|
52
|
+
rubygems_version: 3.3.7
|
52
53
|
signing_key:
|
53
54
|
specification_version: 4
|
54
55
|
summary: Linear optimization for Ruby
|