rulp 0.0.46 → 0.0.50

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: 79e259317b851f96717dc4e0e66b07f08cf035efc420eeef915db1f40d7b5389
4
- data.tar.gz: 68cfcdf7d345b9e1ee0c66b70b822a3edcac50af649a906902ef08db4f673f87
3
+ metadata.gz: 85bd89961eddfceba98245a2ac9882467ae8363209242a3b87bf092afc746d28
4
+ data.tar.gz: 7fdaf681f33655e4fbd461363ca4d94c581362080958b50fef21050f6562d29c
5
5
  SHA512:
6
- metadata.gz: 0fe7414a15de24cd86043956491056cda383ce81ab21a7010add8715fbb2d2a376ff284faade9f7e66a512b1ae3c62926dcceb9dcf9a2dfe3e497eb11f558b8e
7
- data.tar.gz: 3596934464b9aa30a43467fdc08e69a935f8d53219d2edf1b8110ca1806e71d5507802bd79d770df4d17be1d35f905e5b1b2a6554f3cbb4b5e78548864ebe9ee
6
+ metadata.gz: 1e676c5a658047114c59e29bab30fcb43f61a3fe2ddb8156234f034d475a1a2d50e78d232fa006bb723a84e453727857dc5963bee096c9db4f7b033119d6c96c
7
+ data.tar.gz: 58584bcfe586ac5ae4603b54ed1a1c2de520e95bb41a06cb0a553d510fb2c6c914fda5bcb820d8d23348259e9c623face707c5ec06ab75f35ee52a391928a9c8
data/lib/rulp/lv.rb CHANGED
@@ -40,7 +40,7 @@ class LV
40
40
  end
41
41
  end
42
42
 
43
- def * (numeric)
43
+ def *(numeric)
44
44
  self.nocoerce
45
45
  Expressions.new([Fragment.new(self, numeric)])
46
46
  end
@@ -53,7 +53,7 @@ class LV
53
53
  self + (-other)
54
54
  end
55
55
 
56
- def + (expressions)
56
+ def +(expressions)
57
57
  Expressions[self] + Expressions[expressions]
58
58
  end
59
59
 
data/lib/rulp/rulp.rb CHANGED
@@ -18,6 +18,7 @@ GLPK = "glpsol"
18
18
  SCIP = "scip"
19
19
  CBC = "cbc"
20
20
  GUROBI = "gurobi_cl"
21
+ HIGHS = "highs"
21
22
 
22
23
  module Rulp
23
24
  attr_accessor :expressions
@@ -31,12 +32,14 @@ module Rulp
31
32
  GUROBI = ::GUROBI
32
33
  SCIP = ::SCIP
33
34
  CBC = ::CBC
35
+ HIGHS = ::HIGHS
34
36
 
35
37
  SOLVERS = {
36
38
  GLPK => Glpk,
37
39
  SCIP => Scip,
38
40
  CBC => Cbc,
39
41
  GUROBI => Gurobi,
42
+ HIGHS => Highs
40
43
  }
41
44
 
42
45
 
@@ -56,6 +59,10 @@ module Rulp
56
59
  lp.solve_with(GUROBI, opts)
57
60
  end
58
61
 
62
+ def self.Highs(lp, opts={})
63
+ lp.solve_with(HIGHS, opts)
64
+ end
65
+
59
66
  def self.Max(objective_expression)
60
67
  Rulp.log(Logger::INFO, "Creating maximization problem")
61
68
  Problem.new(Rulp::MAX, objective_expression)
@@ -185,6 +192,8 @@ module Rulp
185
192
  solver.store_results(@variables)
186
193
 
187
194
  if solver.unsuccessful
195
+ raise "Solve failed: #{solver.model_status}" if solver.model_status
196
+
188
197
  outfile_contents = IO.read(solver.outfile)
189
198
  raise "Solve failed: solution infeasible" if outfile_contents.downcase.include?("infeasible") || outfile_contents.strip.length.zero?
190
199
  raise "Solve failed: all units undefined"
data/lib/rulp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rulp
2
- VERSION = '0.0.46'
2
+ VERSION = '0.0.50'
3
3
  end
data/lib/solvers/glpk.rb CHANGED
@@ -4,7 +4,7 @@ class Glpk < Solver
4
4
  command %= [
5
5
  options[:gap] ? "--mipgap #{options[:gap]}" : "",
6
6
  options[:time_limit] ? "--tmlim #{options[:time_limit]}" : ""
7
- ]
7
+ ].join(" ")
8
8
  exec(command)
9
9
  end
10
10
 
@@ -30,4 +30,4 @@ class Glpk < Solver
30
30
  self.unsuccessful = rows[-3].downcase.include?('infeasible')
31
31
  return objective_str.to_f
32
32
  end
33
- end
33
+ end
@@ -0,0 +1,70 @@
1
+ class Highs < Solver
2
+ HIGHS_SUPPORTED_OPTIONS = {
3
+ gap: :mip_rel_gap
4
+ }.freeze
5
+
6
+ def solve
7
+ options_file = create_highs_options_file
8
+
9
+ command = "#{executable} --model_file #{@filename} --solution_file #{@outfile} %s %s"
10
+ command %= [
11
+ options[:time_limit] ? "--time_limit #{options[:time_limit]}" : '',
12
+ options_file.length.positive? ? "--options_file #{options_file}" : ''
13
+ ]
14
+
15
+ exec(command)
16
+
17
+ # Remove options file as HiGHS requires additional params in file format instead of command line arguments
18
+ FileUtils.rm(options_file) unless options_file.empty?
19
+ end
20
+
21
+ def self.executable
22
+ :highs
23
+ end
24
+
25
+ def store_results(variables)
26
+ rows = IO.read(@outfile).split("\n")
27
+ self.model_status = rows[1]
28
+
29
+ if model_status.downcase.include?('infeasible') ||
30
+ model_status.downcase.include?('time limit reached')
31
+ self.unsuccessful = true
32
+ return
33
+ end
34
+
35
+ columns_idx = rows.index { |r| r.match?(/# Columns/) }
36
+ rows_idx = rows.index { |r| r.match?(/# Rows/) }
37
+
38
+ vars_by_name = {}
39
+
40
+ rows[(columns_idx + 1)...rows_idx].each do |row|
41
+ var_name, var_value = row.strip.split(/\s+/)
42
+ vars_by_name[var_name] = var_value
43
+ end
44
+
45
+ variables.each do |var|
46
+ var.value = vars_by_name[var.to_s].to_f
47
+ end
48
+
49
+ rows.find { |r| r.match?(/Objective/) }.to_s.split(/\s+/).last.to_f
50
+ end
51
+
52
+ private
53
+
54
+ def create_highs_options_file
55
+ options_str = ''
56
+
57
+ HIGHS_SUPPORTED_OPTIONS.each do |rulp_key, highs_key|
58
+ next unless options[rulp_key]
59
+
60
+ options_str += "#{highs_key} = #{options[rulp_key]}\n"
61
+ end
62
+
63
+ return '' if options_str.empty?
64
+
65
+ options_file = "/tmp/highs-#{Random.rand(0..1_000_000)}.opt"
66
+ IO.write(options_file, options_str)
67
+
68
+ options_file
69
+ end
70
+ end
@@ -1,6 +1,8 @@
1
+ require 'fileutils'
2
+
1
3
  class Solver
2
4
  attr_reader :options, :outfile, :filename
3
- attr_accessor :unsuccessful
5
+ attr_accessor :unsuccessful, :model_status
4
6
 
5
7
  def initialize(filename, options)
6
8
  @options = options
@@ -61,3 +63,4 @@ require_relative 'cbc'
61
63
  require_relative 'scip'
62
64
  require_relative 'glpk'
63
65
  require_relative 'gurobi'
66
+ require_relative 'highs'
@@ -55,4 +55,4 @@ class BasicSuite < Minitest::Test
55
55
  assert_in_delta X_f.value, 345.4321, 0.001
56
56
  end
57
57
  end
58
- end
58
+ end
data/test/test_helper.rb CHANGED
@@ -8,7 +8,7 @@ Rulp::log_level = Logger::UNKNOWN
8
8
  Rulp::print_solver_outputs = false
9
9
 
10
10
  def each_solver
11
- [:scip, :cbc, :glpk, :gurobi].each do |solver|
11
+ [:scip, :cbc, :glpk, :gurobi, :highs].each do |solver|
12
12
  LV::clear
13
13
  if Rulp::solver_exists?(solver)
14
14
  yield(solver)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rulp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.46
4
+ version: 0.0.50
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wouter Coppieters
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-25 00:00:00.000000000 Z
11
+ date: 2024-05-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A simple Ruby LP description DSL
14
14
  email: wc@pico.net.nz
@@ -36,6 +36,7 @@ files:
36
36
  - lib/solvers/cbc.rb
37
37
  - lib/solvers/glpk.rb
38
38
  - lib/solvers/gurobi.rb
39
+ - lib/solvers/highs.rb
39
40
  - lib/solvers/scip.rb
40
41
  - lib/solvers/solver.rb
41
42
  - test/test_basic_suite.rb
@@ -64,7 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
64
65
  - !ruby/object:Gem::Version
65
66
  version: '0'
66
67
  requirements: []
67
- rubygems_version: 3.5.6
68
+ rubygems_version: 3.4.19
68
69
  signing_key:
69
70
  specification_version: 4
70
71
  summary: Ruby Linear Programming