highs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 39ebf3fc768ecb688ec539a334c754f573c5f3f070b184f16a946602f12ad3ab
4
+ data.tar.gz: 856643d58ea6598e0770fdf43d31f1edd01b5715d695774617d933b08ac25182
5
+ SHA512:
6
+ metadata.gz: 75b9b9df7e57ac0a0cd64368bbe68377b6968eb095762c3cd5b645836e162488b38021fe1cb6bd4099f4f41433d6006f67c4bfc847d979f69cb233f494a4ab30
7
+ data.tar.gz: 6efca39cba247de891c5aed30e4d9ca6dc1a0a5c1190b357271d96cf16cbaf8d44873fcd4a8a1c1886245c9cdfea8081bf1a372c3ab32f10a68e000b901fc42f
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.1.0 (2022-04-05)
2
+
3
+ - First release
data/LICENSE.txt ADDED
@@ -0,0 +1,25 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 ERGO-Code
4
+ Copyright (c) 2022 Andrew Kane
5
+
6
+ IPX and BASICLU, Copyright (c) 2018-2021 ERGO-Code
7
+ Used in HiGHS under the MIT license.
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # HiGHS Ruby
2
+
3
+ [HiGHS](https://www.maths.ed.ac.uk/hall/HiGHS/) - linear optimization software - for Ruby
4
+
5
+ [![Build Status](https://github.com/ankane/highs-ruby/workflows/build/badge.svg?branch=master)](https://github.com/ankane/highs-ruby/actions)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application’s Gemfile:
10
+
11
+ ```ruby
12
+ gem "highs"
13
+ ```
14
+
15
+ ## Getting Started
16
+
17
+ *The API is fairly low-level at the moment*
18
+
19
+ Solve a linear program
20
+
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
+ )
34
+ ```
35
+
36
+ Solve a mixed-integer program
37
+
38
+ ```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
+ )
52
+ ```
53
+
54
+ Solve a quadratic program
55
+
56
+ ```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
+ )
73
+ ```
74
+
75
+ ## History
76
+
77
+ View the [changelog](https://github.com/ankane/highs-ruby/blob/master/CHANGELOG.md)
78
+
79
+ ## Contributing
80
+
81
+ Everyone is encouraged to help improve this project. Here are a few ways you can help:
82
+
83
+ - [Report bugs](https://github.com/ankane/highs-ruby/issues)
84
+ - Fix bugs and [submit pull requests](https://github.com/ankane/highs-ruby/pulls)
85
+ - Write, clarify, or fix documentation
86
+ - Suggest or add new features
87
+
88
+ To get started with development:
89
+
90
+ ```sh
91
+ git clone https://github.com/ankane/highs-ruby.git
92
+ cd highs-ruby
93
+ bundle install
94
+ bundle exec rake vendor:all
95
+ bundle exec rake test
96
+ ```
@@ -0,0 +1,37 @@
1
+ module Highs
2
+ class BaseArray
3
+ NOT_SET = Object.new
4
+
5
+ def initialize(size, value = NOT_SET)
6
+ @size = size
7
+ @ptr =
8
+ if value == NOT_SET
9
+ Fiddle::Pointer.malloc(size * self.class::SIZE)
10
+ else
11
+ if value.size != size
12
+ # TODO add variable name to message
13
+ raise ArgumentError, "wrong size (given #{value.size}, expected #{size})"
14
+ end
15
+ Fiddle::Pointer[value.pack("#{self.class::FORMAT}#{size}")]
16
+ end
17
+ end
18
+
19
+ def to_a
20
+ @ptr[0, @size * self.class::SIZE].unpack("#{self.class::FORMAT}#{@size}")
21
+ end
22
+
23
+ def to_ptr
24
+ @ptr
25
+ end
26
+ end
27
+
28
+ class DoubleArray < BaseArray
29
+ FORMAT = "d"
30
+ SIZE = Fiddle::SIZEOF_DOUBLE
31
+ end
32
+
33
+ class IntArray < BaseArray
34
+ FORMAT = "i!"
35
+ SIZE = Fiddle::SIZEOF_INT
36
+ end
37
+ end
data/lib/highs/ffi.rb ADDED
@@ -0,0 +1,40 @@
1
+ module Highs
2
+ module FFI
3
+ extend Fiddle::Importer
4
+
5
+ libs = Array(Highs.ffi_lib).dup
6
+ begin
7
+ dlload Fiddle.dlopen(libs.shift)
8
+ rescue Fiddle::DLError => e
9
+ retry if libs.any?
10
+ raise e
11
+ end
12
+
13
+ # https://github.com/ERGO-Code/HiGHS/blob/master/src/interfaces/highs_c_api.h
14
+
15
+ MODEL_STATUS = [
16
+ :not_set, :load_error, :model_error, :presolve_error, :solve_error, :postsolve_error,
17
+ :model_empty, :optimal, :infeasible, :unbounded_or_infeasible, :unbounded,
18
+ :objective_bound, :objective_target, :time_limit, :iteration_limit, :unknown
19
+ ]
20
+
21
+ MATRIX_FORMAT = {
22
+ colwise: 1,
23
+ rowwise: 2
24
+ }
25
+
26
+ OBJ_SENSE = {
27
+ minimize: 1,
28
+ maximize: -1
29
+ }
30
+
31
+ BASIS_STATUS = [:lower, :basic, :upper, :zero, :nonbasic]
32
+
33
+ typealias "HighsInt", "int"
34
+ typealias "HighsUInt", "unsigned int"
35
+
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
+ 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
+ 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
+ end
40
+ end
@@ -0,0 +1,125 @@
1
+ module Highs
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:)
4
+ num_col = col_cost.size
5
+ num_row = row_lower.size
6
+ num_nz = a_index.size
7
+ a_format = FFI::MATRIX_FORMAT.fetch(a_format)
8
+ sense = FFI::OBJ_SENSE.fetch(sense)
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,
22
+ DoubleArray.new(num_col, col_cost), DoubleArray.new(num_col, col_lower), DoubleArray.new(num_col, col_upper),
23
+ 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
29
+ )
30
+
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
+ }
40
+ end
41
+
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:)
43
+ num_col = col_cost.size
44
+ num_row = row_lower.size
45
+ num_nz = a_index.size
46
+ a_format = FFI::MATRIX_FORMAT.fetch(a_format)
47
+ sense = FFI::OBJ_SENSE.fetch(sense)
48
+
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,
57
+ DoubleArray.new(num_col, col_cost), DoubleArray.new(num_col, col_lower), DoubleArray.new(num_col, col_upper),
58
+ 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
64
+ )
65
+
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
+ }
71
+ end
72
+
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:)
74
+ num_col = col_cost.size
75
+ num_row = row_lower.size
76
+ num_nz = a_index.size
77
+ q_num_nz = q_index.size
78
+ a_format = FFI::MATRIX_FORMAT.fetch(a_format)
79
+ q_format = FFI::MATRIX_FORMAT.fetch(q_format)
80
+ sense = FFI::OBJ_SENSE.fetch(sense)
81
+
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,
95
+ DoubleArray.new(num_col, col_cost), DoubleArray.new(num_col, col_lower), DoubleArray.new(num_col, col_upper),
96
+ 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
103
+ )
104
+
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
+ }
114
+ end
115
+
116
+ private
117
+
118
+ def check_status(status)
119
+ # TODO handle warnings (status = 1)
120
+ if status == -1
121
+ raise Error, "Bad status"
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,3 @@
1
+ module Highs
2
+ VERSION = "0.1.0"
3
+ end
data/lib/highs.rb ADDED
@@ -0,0 +1,39 @@
1
+ # stdlib
2
+ require "fiddle/import"
3
+
4
+ # modules
5
+ require "highs/array"
6
+ require "highs/methods"
7
+ require "highs/version"
8
+
9
+ module Highs
10
+ class Error < StandardError; end
11
+
12
+ extend Methods
13
+
14
+ class << self
15
+ attr_accessor :ffi_lib
16
+ end
17
+ lib_name =
18
+ if Gem.win_platform?
19
+ # uses lib prefix for Windows
20
+ "libhighs.dll"
21
+ elsif RbConfig::CONFIG["host_os"] =~ /darwin/i
22
+ if RbConfig::CONFIG["host_cpu"] =~ /arm|aarch64/i
23
+ "libhighs.arm64.dylib"
24
+ else
25
+ "libhighs.dylib"
26
+ end
27
+ else
28
+ if RbConfig::CONFIG["host_cpu"] =~ /arm|aarch64/i
29
+ "libhighs.arm64.so"
30
+ else
31
+ "libhighs.so"
32
+ end
33
+ end
34
+ vendor_lib = File.expand_path("../vendor/#{lib_name}", __dir__)
35
+ self.ffi_lib = [vendor_lib]
36
+
37
+ # friendlier error message
38
+ autoload :FFI, "highs/ffi"
39
+ end
data/vendor/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 ERGO-Code
4
+
5
+ IPX and BASICLU, Copyright (c) 2018-2021 ERGO-Code
6
+ Used in HiGHS under the MIT license.
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in all
16
+ copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ SOFTWARE.
Binary file
Binary file
Binary file
Binary file
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: highs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Kane
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-04-05 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email: andrew@ankane.org
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - CHANGELOG.md
20
+ - LICENSE.txt
21
+ - README.md
22
+ - lib/highs.rb
23
+ - lib/highs/array.rb
24
+ - lib/highs/ffi.rb
25
+ - lib/highs/methods.rb
26
+ - lib/highs/version.rb
27
+ - vendor/LICENSE
28
+ - vendor/libhighs.arm64.dylib
29
+ - vendor/libhighs.arm64.so
30
+ - vendor/libhighs.dylib
31
+ - vendor/libhighs.so
32
+ homepage: https://github.com/ankane/highs-ruby
33
+ licenses:
34
+ - MIT
35
+ metadata: {}
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '2.7'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubygems_version: 3.1.6
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: Linear optimization for Ruby
55
+ test_files: []