rulp 0.0.46 → 0.0.50

Sign up to get free protection for your applications and to get access to all the features.
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