class MIPPeR::GurobiModel

A linear programming model using the Gurobi solver

Attributes

environment[R]
ptr[R]

Public Class Methods

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

  super

  @var_count = 0

  @environment = Gurobi::Environment.new

  @ptr = FFI::MemoryPointer.new :pointer
  Gurobi.GRBnewmodel @environment.ptr, @ptr, 'model',
                     0, nil, nil, nil, nil, nil
  @ptr = @ptr.read_pointer

  # Ensure the model is freed
  ObjectSpace.define_finalizer self, self.class.finalize(@ptr)
end

Private Class Methods

finalize(ptr) click to toggle source

Free the model

# File lib/mipper/gurobi/model.rb, line 185
def self.finalize(ptr)
  proc { Gurobi.GRBfreemodel ptr }
end

Public Instance Methods

compute_iis() click to toggle source

Compute an irreducible inconsistent subsytem for the model

# File lib/mipper/gurobi/model.rb, line 62
def compute_iis
  ret = Gurobi.GRBcomputeIIS @ptr
  fail if ret != 0
end
optimize() click to toggle source

Optimize the model

# File lib/mipper/gurobi/model.rb, line 51
def optimize
  # Ensure pending variables and constraints are added
  update

  ret = Gurobi.GRBoptimize @ptr
  fail if ret != 0

  save_solution
end
sense=(sense) click to toggle source

Set the sense of the model

# File lib/mipper/gurobi/model.rb, line 43
def sense=(sense)
  @sense = sense
  sense = sense == :min ? Gurobi::GRB_MINIMIZE : Gurobi::GRB_MAXIMIZE
  ret = Gurobi.GRBsetintattr @ptr, Gurobi::GRB_INT_ATTR_MODELSENSE, sense
  fail if ret != 0
end
set_variable_bounds(var_index, lb, ub) click to toggle source
# File lib/mipper/gurobi/model.rb, line 67
def set_variable_bounds(var_index, lb, ub)
  set_double_attribute Gurobi::GRB_DBL_ATTR_LB, var_index, lb
  set_double_attribute Gurobi::GRB_DBL_ATTR_UB, var_index, ub
end
update() click to toggle source

Update the model

Calls superclass method
# File lib/mipper/gurobi/model.rb, line 25
def update
  super

  ret = Gurobi.GRBupdatemodel @ptr
  fail if ret != 0
end
write_lp(filename) click to toggle source

Write the model to a file in CPLEX LP format

# File lib/mipper/gurobi/model.rb, line 33
def write_lp(filename)
  Gurobi.GRBwrite @ptr, filename
end
write_mps(filename) click to toggle source

Write the model to a file in MPS format

# File lib/mipper/gurobi/model.rb, line 38
def write_mps(filename)
  Gurobi.GRBwrite @ptr, filename
end

Protected Instance Methods

add_constraint(constr) click to toggle source

Add a new constraint to the model

# File lib/mipper/gurobi/model.rb, line 163
def add_constraint(constr)
  terms = constr.expression.terms
  indexes_buffer = build_pointer_array(terms.each_key.map do |var|
    var.index
  end, :int)
  values_buffer = build_pointer_array terms.values, :double

  ret = Gurobi.GRBaddconstr @ptr, terms.length,
                            indexes_buffer, values_buffer,
                            gurobi_sense(constr.sense),
                            constr.rhs, constr.name
  fail if ret != 0

  constr.model = self
  constr.index = @constraints.length
  constr.freeze
  @constraints << constr
end
add_constraints(constrs) click to toggle source

Add multiple constraints at once

# File lib/mipper/gurobi/model.rb, line 135
def add_constraints(constrs)
  cbeg, cind, cval = build_constraint_matrix constrs

  cbeg_buffer = build_pointer_array cbeg, :int
  cind_buffer = build_pointer_array cind, :int
  cval_buffer = build_pointer_array cval, :double
  sense_buffer = build_pointer_array(constrs.map do |c|
    gurobi_sense(c.sense)
  end, :char)
  rhs_buffer = build_pointer_array constrs.map(&:rhs), :double

  names_buffer = build_pointer_array array_to_pointers_to_names(constrs),
                                     :pointer

  ret = Gurobi.GRBaddconstrs @ptr, constrs.length, cind.length,
                             cbeg_buffer, cind_buffer, cval_buffer,
                             sense_buffer, rhs_buffer, names_buffer
  fail if ret != 0

  constrs.each do |constr|
    constr.model = self
    constr.index = @constraints.length
    constr.freeze
    @constraints << constr
  end
end
add_variable(var) click to toggle source

Add a new variable to the model

# File lib/mipper/gurobi/model.rb, line 125
def add_variable(var)
  ret = Gurobi.GRBaddvar @ptr, 0, nil, nil, var.coefficient,
                         var.lower_bound, var.upper_bound,
                         gurobi_type(var.type), var.name
  fail if ret != 0

  store_variable var
end
add_variables(vars) click to toggle source

Add multiple variables to the model simultaneously

# File lib/mipper/gurobi/model.rb, line 100
def add_variables(vars)
  objective_buffer = build_pointer_array vars.map(&:coefficient), :double
  lb_buffer = build_pointer_array vars.map(&:lower_bound), :double
  ub_buffer = build_pointer_array vars.map(&:upper_bound), :double
  type_buffer = build_pointer_array(vars.map do |var|
    gurobi_type(var.type)
  end, :char)
  names_buffer = build_pointer_array array_to_pointers_to_names(vars),
                                     :pointer

  ret = Gurobi.GRBaddvars @ptr, vars.length, 0, nil, nil, nil,
                          objective_buffer, lb_buffer, ub_buffer,
                          type_buffer, names_buffer

  fail if ret != 0

  # Store all the variables in the model
  vars.each { |var| store_variable var }

  # Update the model with variables so constraint adds succeed
  ret = Gurobi.GRBupdatemodel @ptr
  fail if ret != 0
end
gurobi_objective() click to toggle source

The value of the objective function

# File lib/mipper/gurobi/model.rb, line 92
def gurobi_objective
  dblptr = FFI::MemoryPointer.new :pointer
  ret = Gurobi.GRBgetdblattr @ptr, Gurobi::GRB_DBL_ATTR_OBJVAL, dblptr
  fail if ret != 0
  dblptr.read_double
end
gurobi_status() click to toggle source

Get the status of the model

# File lib/mipper/gurobi/model.rb, line 75
def gurobi_status
  intptr = FFI::MemoryPointer.new :pointer
  ret = Gurobi.GRBgetintattr @ptr, Gurobi::GRB_INT_ATTR_STATUS, intptr
  fail if ret != 0

  case intptr.read_int
  when Gurobi::GRB_OPTIMAL
    :optimized
  when Gurobi::GRB_INFEASIBLE, Gurobi::GRB_INF_OR_UNBD,
    Gurobi::GRB_UNBOUNDED
    :invalid
  else
    :unknown
  end
end

Private Instance Methods

array_to_pointers_to_names(arr) click to toggle source

Convert an array of objects to an FFI array of memory pointers to the names of each object

# File lib/mipper/gurobi/model.rb, line 243
def array_to_pointers_to_names(arr)
  arr.map do |obj|
    obj.name.nil? ? nil : FFI::MemoryPointer.from_string(obj.name)
  end
end
build_constraint_matrix(constrs) click to toggle source

Construct a matrix of values for the given list of constraints

# File lib/mipper/gurobi/model.rb, line 190
def build_constraint_matrix(constrs)
  cbeg = []
  cind = []
  cval = []
  constrs.each.map do |constr|
    cbeg << cind.length
    constr.expression.terms.each do |var, coeff|
      cind << var.index
      cval << coeff
    end
  end

  [cbeg, cind, cval]
end
gurobi_sense(sense) click to toggle source
# File lib/mipper/gurobi/model.rb, line 249
def gurobi_sense(sense)
  sense.to_s[0].ord
end
gurobi_type(type) click to toggle source
# File lib/mipper/gurobi/model.rb, line 253
def gurobi_type(type)
  case type
  when :integer
    Gurobi::GRB_INTEGER.ord
  when :binary
    Gurobi::GRB_BINARY.ord
  when :continuous
    Gurobi::GRB_CONTINUOUS.ord
  else
    fail type
  end
end
save_solution() click to toggle source

Save the solution to the model for access later

# File lib/mipper/gurobi/model.rb, line 206
def save_solution
  status = gurobi_status

  if status == :optimized
    objective_value = gurobi_objective
    variable_values = @variables.map do |var|
      dblptr = FFI::MemoryPointer.new :pointer
      Gurobi.GRBgetdblattrarray @ptr, Gurobi::GRB_DBL_ATTR_X,
                                var.index, 1, dblptr
      dblptr.read_array_of_double(1)[0]
    end
  else
    objective_value = nil
    variable_values = []
  end

  @solution = Solution.new status, objective_value, variable_values
end
set_double_attribute(name, var_index, value) click to toggle source
# File lib/mipper/gurobi/model.rb, line 235
def set_double_attribute(name, var_index, value)
  buffer = build_pointer_array [value], :double
  ret = Gurobi.GRBsetdblattrarray @ptr, name, var_index, 1, buffer
  fail if ret != 0
end
store_variable(var) click to toggle source

Save the variable to the model and update the variable pointers

# File lib/mipper/gurobi/model.rb, line 226
def store_variable(var)
  # Update the variable to track the index in the model
  var.model = self
  var.index = @var_count
  @var_count += 1

  @variables << var
end