Class: LpSolver::Drivers::NativeDriver

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

Overview

Solves a model using the native C extension.

This driver builds HiGHS data structures directly from the model and calls the native C API, bypassing LP file serialization. It requires the native extension to be compiled and loaded.

Instance Method Summary collapse

Instance Method Details

#solve(model) ⇒ Solution

Solves the model using the native C extension.

Parameters:

  • model (Model)

    The model to solve.

Returns:

Raises:

  • (LoadError)

    If the native extension is not available.

  • (SolverError)

    If the native solver encounters an error.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/lpsolver/drivers/native_driver.rb', line 18

def solve(model)
  unless defined?(LpSolver::HiGhSSolver)
    raise LoadError, 'Native extension not available. Compile with: rake compile'
  end

  num_col = model.var_counter
  num_row = model.constraints.size

  # Determine heading — native API always minimizes, so negate for maximize
  heading = model.heading == :maximize ? :maximize : :minimize

  # 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)

  model.var_bounds.each do |name, (lb, ub)|
    idx = model.variables[name].index
    col_lower[idx] = lb
    col_upper[idx] = ub
    col_integrality[idx] = model.var_types[name] == :integer ? 1 : 0
  end

  model.objective.each do |idx, coeff|
    col_cost[idx] = heading == :maximize ? -coeff.to_f : coeff.to_f
  end

  # Build constraint arrays
  row_lower = Array.new(num_row)
  row_upper = Array.new(num_row)

  model.constraints.each_with_index do |constr, row_idx|
    row_lower[row_idx] = constr[:lb]
    row_upper[row_idx] = constr[:ub]
  end

  # Build matrix in CSC (column-wise) format
  # a_start[col] = index into a_index/a_value where column `col` starts
  # a_index[nz] = row index of non-zero element
  # a_value[nz] = value of non-zero element
  nz_per_col = Array.new(num_col, 0)
  nz_entries = []

  model.constraints.each_with_index do |constr, row_idx|
    constr[:expr].each do |col_idx, coeff|
      nz_entries << [col_idx, row_idx, coeff.to_f]
      nz_per_col[col_idx] += 1
    end
  end

  total_nz = nz_entries.size

  # Build a_start (cumulative column starts)
  a_start = [0]
  nz_per_col.each { |count| a_start << a_start.last + count }

  # Sort entries by column index for CSC format
  nz_entries.sort_by! { |col, row, _| col }

  aindex = nz_entries.map { |_, row, _| row }
  avalues = nz_entries.map { |_, _, val| val }

  # Call native solver (skip if no columns/rows)
  if num_col > 0 && num_row > 0
    result = call_native(
      num_col, num_row, total_nz,
      col_cost, col_lower, col_upper, col_integrality,
      row_lower, row_upper,
      a_start, aindex, avalues
    )
  else
    result = { status: :unbounded, objective: 0.0, col_value: [] }
  end

  # Parse result — variables are empty for infeasible/unbounded
  variables = {}
  unless [:infeasible, :unbounded, :unbounded_or_infeasible].include?(result[:status])
    result[:col_value].each_with_index do |val, idx|
      var_name = model.variables.find { |_, v| v.index == idx }&.first
      variables[var_name.to_s] = val if var_name
    end
  end

  Solution.new(
    variables: variables,
    objective_value: heading == :maximize ? -result[:objective] : result[:objective],
    model_status: result[:status].to_s,
    iterations: 0
  )
end