highs 0.1.0

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 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: []