class MIPPeR::CbcModel

A linear programming model using the COIN-OR solver

Attributes

ptr[R]

Public Class Methods

new() click to toggle source
Calls superclass method
# File lib/mipper/cbc/model.rb, line 8
def initialize
  fail unless MIPPeR.const_defined?(:Cbc)

  super

  @var_count = 0
  @constr_count = 0

  # Construct a new model
  @ptr = new_model
end

Public Instance Methods

optimize() click to toggle source

Optimize the model

# File lib/mipper/cbc/model.rb, line 52
def optimize
  # Ensure pending variables and constraints are added
  parent_update

  # Run the solver and save the status for later
  Cbc.Cbc_solve @ptr
  fail if Cbc.Cbc_status(@ptr) != 0

  save_solution

  @ptr = new_model
  reset_model
end
parent_update()
Alias for: update
sense=(sense) click to toggle source

Set the sense of the model

# File lib/mipper/cbc/model.rb, line 45
def sense=(sense)
  @sense = sense
  sense = sense == :min ? 1 : -1
  Cbc.Cbc_setObjSense @ptr, sense
end
set_variable_bounds(var_index, lb, ub, force = false) click to toggle source

Set the bounds of a variable in the model

# File lib/mipper/cbc/model.rb, line 67
def set_variable_bounds(var_index, lb, ub, force = false)
  # This is a bit of a hack so that we don't try to set
  # the variable bounds before they get added to the model
  return unless force

  Cbc.Cbc_setColLower @ptr, var_index, lb
  Cbc.Cbc_setColUpper @ptr, var_index, ub
end
update() click to toggle source

Avoid doing anything here. Updating multiple times will break the model so we defer to solve.

# File lib/mipper/cbc/model.rb, line 41
def update
end
Also aliased as: parent_update
write_mps(filename) click to toggle source

Write the model to a file in MPS format

# File lib/mipper/cbc/model.rb, line 21
def write_mps(filename)
  # Make a new model and ensure everything is added
  old_ptr = @ptr
  @ptr = new_model
  parent_update

  Cbc.Cbc_writeMps @ptr, filename.chomp('.mps')
  contents = Zlib::GzipReader.open(filename + '.gz').read
  File.delete(filename + '.gz')
  File.open(filename, 'w').write contents

  # Reset to the original model
  @ptr = old_ptr
  reset_model
end

Protected Instance Methods

add_constraints(constrs) click to toggle source

Add multiple constraints at once

# File lib/mipper/cbc/model.rb, line 90
def add_constraints(constrs)
  start, index, value = build_constraint_matrix constrs
  start_buffer = build_pointer_array start, :int
  index_buffer = build_pointer_array index, :int
  value_buffer = build_pointer_array value, :double

  Cbc.Cbc_loadProblem @ptr, @variables.length, constrs.length,
                      start_buffer, index_buffer, value_buffer,
                      nil, nil, nil, nil, nil

  store_model constrs, @variables
end
add_variables(vars) click to toggle source

Add multiple variables to the model simultaneously

# File lib/mipper/cbc/model.rb, line 79
def add_variables(vars)
  # Store all the variables in the model
  # Most of the work will be done when we add the constraints
  vars.each do |var|
    var.model = self
    var.index = @variables.count
    @variables << var
  end
end

Private Instance Methods

build_constraint_matrix(constrs) click to toggle source

Build a constraint matrix for the currently existing variables

# File lib/mipper/cbc/model.rb, line 128
def build_constraint_matrix(constrs)
  store_constraint_indexes constrs

  # Construct a matrix of non-zero values in CSC format
  start = []
  index = []
  value = []
  col_start = 0
  @variables.each do |var|
    # Mark the start of this column
    start << col_start

    var.constraints.each do |constr|
      col_start += 1
      index << constr.index
      value << constr.expression.terms[var]
    end
  end
  start << col_start

  [start, index, value]
end
new_model() click to toggle source

Construct a new model object

# File lib/mipper/cbc/model.rb, line 106
def new_model
  ptr = FFI::AutoPointer.new Cbc.Cbc_newModel,
                             Cbc.method(:Cbc_deleteModel)

  # Older versions of COIN-OR do not support setting the log level via
  # the C interface in which case setParameter will not be defined
  Cbc.Cbc_setParameter ptr, 'logLevel', '0' \
    if Cbc.respond_to?(:Cbc_setParameter)

  ptr
end
reset_model() click to toggle source

Reset the internal state of the model so we can reuse it

# File lib/mipper/cbc/model.rb, line 192
def reset_model
  @var_count = 0
  @constr_count = 0
  @pending_variables = @variables
  @pending_constraints = @constraints
  @variables = []
  @constraints = []
end
save_solution() click to toggle source

Save the solution to the model for access later

# File lib/mipper/cbc/model.rb, line 168
def save_solution
  # Check and store the model status
  if Cbc.Cbc_isProvenOptimal(@ptr) == 1
    status = :optimized
  elsif Cbc.Cbc_isProvenInfeasible(@ptr) == 1 ||
        Cbc.Cbc_isContinuousUnbounded(@ptr) == 1
    status = :invalid
  else
    status = :unknown
  end

  if status == :optimized
    objective_value = Cbc.Cbc_getObjValue @ptr
    dblptr = Cbc.Cbc_getColSolution @ptr
    variable_values = dblptr.read_array_of_double(@variables.length)
  else
    objective_value = nil
    variable_values = []
  end

  @solution = Solution.new status, objective_value, variable_values
end
set_variable_type(index, type) click to toggle source

Set the type of a variable

# File lib/mipper/cbc/model.rb, line 243
def set_variable_type(index, type)
  case type
  when :continuous
    Cbc.Cbc_setContinuous @ptr, index
  when :integer, :binary
    Cbc.Cbc_setInteger @ptr, index
  else
    fail :type
  end
end
store_constraint(constr) click to toggle source

Save the constraint to the model and update the constraint pointers

# File lib/mipper/cbc/model.rb, line 202
def store_constraint(constr)
  # Update the constraint to track the index in the model
  constr.model = self

  # Set constraint properties
  Cbc.Cbc_setRowName(@ptr, constr.index, constr.name) unless constr.name.nil?
  store_constraint_bounds constr.index, constr.sense, constr.rhs
end
store_constraint_bounds(index, sense, rhs) click to toggle source

Store the bounds for a given constraint

# File lib/mipper/cbc/model.rb, line 212
def store_constraint_bounds(index, sense, rhs)
  case sense
  when :==
    lb = ub = rhs
  when :>=
    lb = rhs
    ub = Float::INFINITY
  when :<=
    lb = -Float::INFINITY
    ub = rhs
  end

  Cbc.Cbc_setRowLower @ptr, index, lb
  Cbc.Cbc_setRowUpper @ptr, index, ub
end
store_constraint_indexes(constrs) click to toggle source

Store the index which will be used for each constraint

# File lib/mipper/cbc/model.rb, line 119
def store_constraint_indexes(constrs)
  constrs.each do |constr|
    constr.model = self
    constr.index = @constr_count
    @constr_count += 1
  end
end
store_model(constrs, vars) click to toggle source

Store all data for the model

# File lib/mipper/cbc/model.rb, line 152
def store_model(constrs, vars)
  # Store all constraints
  constrs.each do |constr|
    store_constraint constr
    @constraints << constr
  end

  # We store variables now since they didn't exist earlier
  vars.each_with_index do |var, i|
    var.index = i
    var.model = self
    store_variable var
  end
end
store_variable(var) click to toggle source

Set the properties of a variable in the model

# File lib/mipper/cbc/model.rb, line 229
def store_variable(var)
  # Force the correct bounds since we can't explicitly specify binary
  if var.type == :binary
    var.instance_variable_set(:@lower_bound, 0)
    var.instance_variable_set(:@upper_bound, 1)
  end
  set_variable_bounds var.index, var.lower_bound, var.upper_bound, true

  Cbc.Cbc_setObjCoeff @ptr, var.index, var.coefficient
  Cbc.Cbc_setColName(@ptr, var.index, var.name) unless var.name.nil?
  set_variable_type var.index, var.type
end