Class: LpSolver::NativeModel
- Inherits:
-
Object
- Object
- LpSolver::NativeModel
- Defined in:
- lib/lpsolver/native_model.rb
Overview
This is a prototype. The native extension must be compiled separately using ‘rake compile` or `ruby ext/lpsolver/extconf.rb && make`.
A native (C extension) backed model solver for HiGHS.
This class uses the native C extension to call HiGHS directly, bypassing the LP file serialization overhead of the CLI approach. It requires the native extension to be compiled and linked against the HiGHS library.
Instance Attribute Summary collapse
-
#constraints ⇒ Array<Hash>
readonly
Constraint data for each constraint.
-
#name ⇒ String
readonly
A descriptive name for this model.
-
#objective ⇒ Hash{Integer => Float}
readonly
Linear objective coefficients.
-
#quadratic_terms ⇒ Array<[Integer, Integer, Float]>
readonly
Quadratic terms (for QP).
-
#sense ⇒ Symbol
readonly
Optimization sense (:minimize or :maximize).
-
#variables ⇒ Hash{Symbol => Variable}
readonly
All variables in the model.
Instance Method Summary collapse
-
#add_constraint(name, expr) ⇒ Symbol
Adds a constraint to the model.
-
#add_variable(name, lb: 0.0, ub: Float::INFINITY, integer: false) ⇒ Variable
Adds a variable to the model.
-
#initialize(name = nil) ⇒ NativeModel
constructor
Creates a new empty model.
-
#maximize ⇒ void
Sets the optimization sense to maximization.
-
#maximize!(objective) ⇒ Solution
Sets the optimization sense, objective, and solves in one call.
-
#minimize ⇒ void
Sets the optimization sense to minimization.
-
#minimize!(objective) ⇒ Solution
Sets the optimization sense, objective, and solves in one call.
-
#set_objective(objective) ⇒ void
Sets the objective function.
-
#solve ⇒ Solution
Solves the model using the native extension.
Constructor Details
#initialize(name = nil) ⇒ NativeModel
Creates a new empty model.
57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/lpsolver/native_model.rb', line 57 def initialize(name = nil) @name = name || 'untitled' @variables = {} @constraints = [] @var_counter = 0 @sense = :minimize @objective = {} @quadratic_terms = [] @var_types = {} @var_bounds = {} end |
Instance Attribute Details
#constraints ⇒ Array<Hash> (readonly)
Returns Constraint data for each constraint.
43 44 45 |
# File 'lib/lpsolver/native_model.rb', line 43 def constraints @constraints end |
#name ⇒ String (readonly)
Returns A descriptive name for this model.
37 38 39 |
# File 'lib/lpsolver/native_model.rb', line 37 def name @name end |
#objective ⇒ Hash{Integer => Float} (readonly)
Returns Linear objective coefficients.
49 50 51 |
# File 'lib/lpsolver/native_model.rb', line 49 def objective @objective end |
#quadratic_terms ⇒ Array<[Integer, Integer, Float]> (readonly)
Returns Quadratic terms (for QP).
52 53 54 |
# File 'lib/lpsolver/native_model.rb', line 52 def quadratic_terms @quadratic_terms end |
#sense ⇒ Symbol (readonly)
Returns Optimization sense (:minimize or :maximize).
46 47 48 |
# File 'lib/lpsolver/native_model.rb', line 46 def sense @sense end |
#variables ⇒ Hash{Symbol => Variable} (readonly)
Returns All variables in the model.
40 41 42 |
# File 'lib/lpsolver/native_model.rb', line 40 def variables @variables end |
Instance Method Details
#add_constraint(name, expr) ⇒ Symbol
Adds a constraint to the model.
92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/lpsolver/native_model.rb', line 92 def add_constraint(name, expr) name = name.to_sym lb_val, ub_val = expr.bounds data_expr = expr.expr @constraints << { name: name, lb: lb_val, ub: ub_val, expr: data_expr } name end |
#add_variable(name, lb: 0.0, ub: Float::INFINITY, integer: false) ⇒ Variable
Adds a variable to the model.
76 77 78 79 80 81 82 83 84 85 |
# File 'lib/lpsolver/native_model.rb', line 76 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 ? 1 : 0 @var_bounds[name] = [lb, ub] @var_counter += 1 var end |
#maximize ⇒ void
This method returns an undefined value.
Sets the optimization sense to maximization.
116 117 118 |
# File 'lib/lpsolver/native_model.rb', line 116 def maximize @sense = :maximize end |
#maximize!(objective) ⇒ Solution
Sets the optimization sense, objective, and solves in one call.
255 256 257 258 259 |
# File 'lib/lpsolver/native_model.rb', line 255 def maximize!(objective) @sense = :maximize set_objective(objective) solve end |
#minimize ⇒ void
This method returns an undefined value.
Sets the optimization sense to minimization.
109 110 111 |
# File 'lib/lpsolver/native_model.rb', line 109 def minimize @sense = :minimize end |
#minimize!(objective) ⇒ Solution
Sets the optimization sense, objective, and solves in one call.
245 246 247 248 249 |
# File 'lib/lpsolver/native_model.rb', line 245 def minimize!(objective) @sense = :minimize set_objective(objective) solve end |
#set_objective(objective) ⇒ void
This method returns an undefined value.
Sets the objective function.
124 125 126 127 128 129 130 131 132 |
# File 'lib/lpsolver/native_model.rb', line 124 def set_objective(objective) if objective.is_a?(QuadraticExpression) @objective = objective.linear_terms.transform_values(&:to_f) @quadratic_terms = objective.hessian_entries elsif objective.is_a?(LinearExpression) @objective = objective.terms.transform_values(&:to_f) @quadratic_terms = [] end end |
#solve ⇒ Solution
Solves the model using the native extension.
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/lpsolver/native_model.rb', line 139 def solve unless defined?(LpSolver::Native) raise LoadError, 'Native extension not available. Compile with: rake compile' end num_col = @var_counter num_row = @constraints.size # Build column arrays col_cost = Array.new(num_col, 0.0) col_lower = Array.new(num_col) col_upper = Array.new(num_col) col_integrality = Array.new(num_col, 0) @var_bounds.each do |name, (lb, ub)| idx = @variables[name].index col_lower[idx] = lb col_upper[idx] = ub col_integrality[idx] = @var_types[name] || 0 end @objective.each do |idx, coeff| col_cost[idx] = coeff end # Build constraint arrays (sparse matrix in CSC format) row_lower = Array.new(num_row) row_upper = Array.new(num_row) astart = Array.new(num_row + 1, 0) aindex = [] avalues = [] nz_count = 0 @constraints.each_with_index do |constr, row_idx| row_lower[row_idx] = constr[:lb] row_upper[row_idx] = constr[:ub] constr[:expr].each do |col_idx, coeff| aindex << col_idx avalues << coeff astart[row_idx + 1] += 1 nz_count += 1 end end # Adjust astart to be cumulative cumulative = 0 astart.each_with_index do |val, i| old = astart[i] astart[i] = cumulative cumulative += val end # Determine sense sense = @sense == :maximize ? :maximize : :minimize # Build quadratic arrays (for QP) q_start = [0] q_index = [] q_values = [] @quadratic_terms.each do |i1, i2, coeff| q_index << i2 q_values << coeff q_start << q_index.size end # Call native solver if @quadratic_terms.any? result = LpSolver::Native.solve_qp( num_col, num_row, col_cost, col_lower, col_upper, row_lower, row_upper, astart, aindex, avalues, q_start, q_index, q_values, sense ) else result = LpSolver::Native.solve_lp( num_col, num_row, col_cost, col_lower, col_upper, col_integrality, row_lower, row_upper, astart, aindex, avalues, sense ) end # Parse result variables = {} result[:col_value].each_with_index do |val, idx| var_name = @variables.find { |_, v| v.index == idx }&.first variables[var_name.to_s] = val if var_name end Solution.new( variables: variables, objective_value: result[:objective], model_status: result[:status].to_s, iterations: 0 ) end |