Class: LpSolver::Model

Inherits:
Object
  • Object
show all
Defined in:
lib/lpsolver/model.rb

Overview

A high-level interface to HiGHS for building and solving LP/QP/MIP models.

The Model class provides a Ruby DSL for defining variables, constraints, and objectives. Models are solved via a pluggable driver.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name = nil) ⇒ Model

Creates a new empty LP/QP/MIP model.

Parameters:

  • name (String) (defaults to: nil)

    An optional name for this model, used for debugging and identification in logs. Defaults to 'untitled'.



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/lpsolver/model.rb', line 85

def initialize(name = nil)
  @name = name || 'untitled'
  @variables = {}       # { symbol => Variable }
  @constraints = {}     # { symbol => index }
  @var_counter = 0
  @constr_counter = 0
  @heading = :minimize
  @solution = nil
  @objective = {}       # { var_index => coefficient }
  @quadratic_terms = [] # [[var1_idx, var2_idx, coefficient], ...]
  @var_types = {}       # { symbol => :continuous | :integer }
  @var_bounds = {}      # { symbol => [lb, ub] }
  @constraints_data = {} # { symbol => { lb:, ub:, expr: [[var_idx, coeff], ...] } }
  @driver = self.class.default_driver
end

Instance Attribute Details

#constraints_dataHash{Symbol => Hash} (readonly)

Returns Maps constraint names to their data.

Returns:

  • (Hash{Symbol => Hash})

    Maps constraint names to their data.



21
22
23
# File 'lib/lpsolver/model.rb', line 21

def constraints_data
  @constraints_data
end

#driverDrivers::CliDriver, Drivers::NativeDriver

Returns The configured solver driver.

Returns:



33
34
35
# File 'lib/lpsolver/model.rb', line 33

def driver
  @driver
end

#headingSymbol (readonly)

Returns The current optimization heading.

Returns:

  • (Symbol)

    The current optimization heading.



15
16
17
# File 'lib/lpsolver/model.rb', line 15

def heading
  @heading
end

#nameString (readonly)

Returns The model name.

Returns:

  • (String)

    The model name.



31
32
33
# File 'lib/lpsolver/model.rb', line 31

def name
  @name
end

#objectiveHash{Integer => Float} (readonly)

Returns Maps variable indices to coefficients.

Returns:

  • (Hash{Integer => Float})

    Maps variable indices to coefficients.



25
26
27
# File 'lib/lpsolver/model.rb', line 25

def objective
  @objective
end

#quadratic_termsArray<[Integer, Integer, Float]> (readonly)

Returns Quadratic term entries.

Returns:

  • (Array<[Integer, Integer, Float]>)

    Quadratic term entries.



27
28
29
# File 'lib/lpsolver/model.rb', line 27

def quadratic_terms
  @quadratic_terms
end

#var_boundsHash{Symbol => Array<Float>} (readonly)

Returns Maps variable names to [lb, ub] bounds.

Returns:

  • (Hash{Symbol => Array<Float>})

    Maps variable names to [lb, ub] bounds.



29
30
31
# File 'lib/lpsolver/model.rb', line 29

def var_bounds
  @var_bounds
end

#var_counterInteger (readonly)

Returns The next available variable index.

Returns:

  • (Integer)

    The next available variable index.



13
14
15
# File 'lib/lpsolver/model.rb', line 13

def var_counter
  @var_counter
end

#var_typesHash{Symbol => Symbol} (readonly)

Returns Maps variable names to their types.

Returns:

  • (Hash{Symbol => Symbol})

    Maps variable names to their types.



23
24
25
# File 'lib/lpsolver/model.rb', line 23

def var_types
  @var_types
end

#variablesHash{Symbol => Variable} (readonly)

Returns All variables defined in this model.

Returns:

  • (Hash{Symbol => Variable})

    All variables defined in this model.



11
12
13
# File 'lib/lpsolver/model.rb', line 11

def variables
  @variables
end

Class Method Details

.default_driverDrivers::CliDriver, Drivers::NativeDriver

Returns the default solver driver.

Tries CliDriver first (if HiGHS binary is available), then falls back to NativeDriver (if native extension is compiled), then raises an error.

Returns:

Raises:

  • (RuntimeError)

    If neither CLI nor native driver is available.



42
43
44
45
46
47
48
49
50
51
52
# File 'lib/lpsolver/model.rb', line 42

def self.default_driver
  begin
    Drivers::CliDriver.new
  rescue
    begin
      Drivers::NativeDriver.new
    rescue LoadError
      raise 'No solver available. Install HiGHS or compile the native extension with: rake compile'
    end
  end
end

Instance Method Details

#add_constraint(name, expr, lb: -Float::INFINITY,, ub: Float::INFINITY) ⇒ Symbol

Adds a constraint to the model.

Parameters:

  • name (Symbol, String)

    The constraint name.

  • expr (ConstraintSpec, Array<[Integer, Float]>)

    The constraint specification (DSL expression or legacy array format).

  • lb (Float) (defaults to: -Float::INFINITY,)

    Lower bound (default: -Inf).

  • ub (Float) (defaults to: Float::INFINITY)

    Upper bound (default: +Inf).

Returns:

  • (Symbol)

    The constraint name.



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/lpsolver/model.rb', line 127

def add_constraint(name, expr, lb: -Float::INFINITY, ub: Float::INFINITY)
  name = name.to_sym

  if expr.is_a?(ConstraintSpec)
    lb_val, ub_val = expr.bounds
    data_expr = expr.expr
  else
    lb_val = lb
    ub_val = ub
    data_expr = expr
  end

  idx = @constr_counter
  @constraints[name] = idx
  @constraints_data[name] = {
    lb: normalize_bound(lb_val),
    ub: normalize_bound(ub_val),
    expr: data_expr
  }
  @constr_counter += 1
  name
end

#add_variable(name, lb: 0.0, ub: Float::INFINITY, integer: false) ⇒ Variable

Adds a variable to the model.

Parameters:

  • name (Symbol, String)

    The variable name.

  • lb (Float) (defaults to: 0.0)

    Lower bound (default: 0.0).

  • ub (Float) (defaults to: Float::INFINITY)

    Upper bound (default: +Inf).

  • integer (Boolean) (defaults to: false)

    Whether the variable is integer (default: false).

Returns:



108
109
110
111
112
113
114
115
116
117
# File 'lib/lpsolver/model.rb', line 108

def add_variable(name, lb: 0.0, ub: Float::INFINITY, integer: false)
  name = name.to_sym
  idx = @var_counter
  var = Variable.new(idx, name)
  @variables[name] = var
  @var_types[name] = integer ? :integer : :continuous
  @var_bounds[name] = [normalize_bound(lb), normalize_bound(ub)]
  @var_counter += 1
  var
end

#constraintsArray<Hash>

Returns Constraint data as an array of lb, ub, expr.

Returns:

  • (Array<Hash>)

    Constraint data as an array of lb, ub, expr.



17
18
19
# File 'lib/lpsolver/model.rb', line 17

def constraints
  @constraints_data.values
end

#maximize!(objective) ⇒ Solution

Sets heading to maximize, sets the objective, and solves.

Parameters:

Returns:

Raises:



166
167
168
169
170
# File 'lib/lpsolver/model.rb', line 166

def maximize!(objective)
  @heading = :maximize
  set_objective_internal(objective)
  solve
end

#minimize!(objective) ⇒ Solution

Sets heading to minimize, sets the objective, and solves.

Parameters:

Returns:

Raises:



155
156
157
158
159
# File 'lib/lpsolver/model.rb', line 155

def minimize!(objective)
  @heading = :minimize
  set_objective_internal(objective)
  solve
end

#solveSolution

Solves the model using the configured driver, with automatic fallback.

If the configured driver is CliDriver and the HiGHS binary is not found, automatically falls back to NativeDriver. If NativeDriver is also not available, raises the original error.

Returns:

Raises:

  • (SolverError)

    If the solver encounters an error.

  • (LoadError)

    If no driver is available.



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/lpsolver/model.rb', line 63

def solve
  begin
    @driver.solve(self)
  rescue SolverError, LoadError => e
    # If CLI driver fails and native driver is available, try it
    if @driver.class.name.end_with?('CliDriver')
      begin
        @driver = Drivers::NativeDriver.new
        @driver.solve(self)
      rescue LoadError
        raise e
      end
    else
      raise e
    end
  end
end

#to_lpString

Converts the model to HiGHS LP format string.

Delegates to LpGenerator for serialization.

Examples:

puts model.to_lp
# Minimize
#  obj: 3 x + 5 y
# Subject To
#  budget: 2 x + 1 y <= 100
#  demand: 1 x + 2 y >= 50
# Bounds
#  0 <= x <= +Inf
#  0 <= y <= +Inf
# End

Returns:

  • (String)

    The LP format content.



208
209
210
# File 'lib/lpsolver/model.rb', line 208

def to_lp
  LpGenerator.new(self).generate
end

#write_lp(filename) ⇒ void

This method returns an undefined value.

Writes the model to an LP file.

Examples:

model.write_lp('my_model.lp')

Parameters:

  • filename (String)

    The output file path.



218
219
220
# File 'lib/lpsolver/model.rb', line 218

def write_lp(filename)
  File.write(filename, to_lp)
end