Class: Quicopt::Model

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

Overview

An optimization model: variables, one objective, and constraint rows.

m = Quicopt::Model.new
x = m.int_var(0, 4, "x")
y = m.bin_var("y")
m.maximize(3 * x + 2 * y)
m.add(x + y <= 5)

You build the model; to_program / encode turn it into the message the service reads, and Quicopt::Client#solve sends it.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeModel

Returns a new instance of Model.



467
468
469
470
471
472
473
# File 'lib/quicopt/model.rb', line 467

def initialize
  @variables = []
  @by_name = {}
  @constraints = []
  @objective = Expression.new
  @sense = "min"
end

Instance Attribute Details

#constraintsArray<Constraint> (readonly)

Returns the constraint rows, in insertion order.

Returns:

  • (Array<Constraint>)

    the constraint rows, in insertion order



461
462
463
# File 'lib/quicopt/model.rb', line 461

def constraints
  @constraints
end

#objectiveExpression (readonly)

Returns the objective (the zero expression until set).

Returns:

  • (Expression)

    the objective (the zero expression until set)



463
464
465
# File 'lib/quicopt/model.rb', line 463

def objective
  @objective
end

#senseString (readonly)

Returns "min" or "max".

Returns:

  • (String)

    "min" or "max"



465
466
467
# File 'lib/quicopt/model.rb', line 465

def sense
  @sense
end

#variablesArray<Variable> (readonly)

Returns the declared variables, in declaration order.

Returns:

  • (Array<Variable>)

    the declared variables, in declaration order



459
460
461
# File 'lib/quicopt/model.rb', line 459

def variables
  @variables
end

Instance Method Details

#add(constraint) ⇒ Constraint

Add a constraint row built by <=, >= or ==.

Parameters:

Returns:

Raises:

  • (TypeError)

    if handed something that is not a constraint — most often a bare expression, or a comparison Ruby already reduced to a boolean



535
536
537
538
539
540
541
542
543
# File 'lib/quicopt/model.rb', line 535

def add(constraint)
  unless constraint.is_a?(Constraint)
    raise TypeError,
          "add expects a constraint such as `x + y <= 5`, got #{constraint.class}"
  end
  check_ownership(constraint.expr)
  @constraints << constraint
  constraint
end

#bin_var(name = nil, start: nil) ⇒ Variable

Declare a binary variable.

Returns:



504
505
506
# File 'lib/quicopt/model.rb', line 504

def bin_var(name = nil, start: nil)
  declare(:BINARY, 0.0, 1.0, name, start)
end

#encodeString

Turn this model into the message the service reads and encode it.

Returns:

  • (String)

    binary protobuf bytes



555
556
557
# File 'lib/quicopt/model.rb', line 555

def encode
  Wire.encode(self)
end

#inspectString

Returns a one-line summary: variable and row counts.

Returns:

  • (String)

    a one-line summary: variable and row counts



571
572
573
# File 'lib/quicopt/model.rb', line 571

def inspect
  "#<Quicopt::Model #{@variables.size} vars, #{@constraints.size} constraints>"
end

#int_var(lower = nil, upper = nil, name = nil, start: nil) ⇒ Variable

Declare an integer variable.

Bounds of exactly [0, 1] declare a :BINARY variable rather than an integer one — the same rule the Python and Julia clients apply, so the same model means the same thing whichever language wrote it.

Returns:



494
495
496
497
498
499
# File 'lib/quicopt/model.rb', line 494

def int_var(lower = nil, upper = nil, name = nil, start: nil)
  lo = coerce_bound(lower, -Float::INFINITY)
  hi = coerce_bound(upper, Float::INFINITY)
  domain = lo.zero? && hi == 1.0 ? :BINARY : :INTEGER
  declare(domain, lo, hi, name, start)
end

#maximize(expr) ⇒ Expression

Maximize expr.

Returns:



525
526
527
# File 'lib/quicopt/model.rb', line 525

def maximize(expr)
  set_objective(expr, "max")
end

#minimize(expr) ⇒ Expression

Minimize expr.

Returns:



518
519
520
# File 'lib/quicopt/model.rb', line 518

def minimize(expr)
  set_objective(expr, "min")
end

#num_var(lower = nil, upper = nil, name = nil, start: nil) ⇒ Variable

Declare a continuous variable.

Parameters:

  • lower (Numeric, nil) (defaults to: nil)

    the lower bound; nil means unbounded below

  • upper (Numeric, nil) (defaults to: nil)

    the upper bound; nil means unbounded above

  • name (String, nil) (defaults to: nil)

    the variable's name; auto-generated when omitted

  • start (Numeric, nil) (defaults to: nil)

    the starting point; defaults to 0 clamped into the bounds, matching the Python and Julia clients

Returns:



483
484
485
# File 'lib/quicopt/model.rb', line 483

def num_var(lower = nil, upper = nil, name = nil, start: nil)
  declare(:CONTINUOUS, lower, upper, name, start)
end

#to_programQuicopt::Modeler::V1::Program

Turn this model into the structured message the service reads.

Returns:

  • (Quicopt::Modeler::V1::Program)


548
549
550
# File 'lib/quicopt/model.rb', line 548

def to_program
  Wire.program(self)
end

#to_sObject

Renders the whole model: objective, rows, then declarations.



560
561
562
563
564
565
566
567
568
# File 'lib/quicopt/model.rb', line 560

def to_s
  lines = ["#{@sense} #{@objective}"]
  lines << "subject to" unless @constraints.empty?
  @constraints.each { |c| lines << "  #{c}" }
  @variables.each do |v|
    lines << "  #{v.name} in #{v.domain} [#{Format.number(v.lower)}, #{Format.number(v.upper)}]"
  end
  lines.join("\n")
end

#variable(name) ⇒ Variable?

Look a variable up by name.

Returns:



511
512
513
# File 'lib/quicopt/model.rb', line 511

def variable(name)
  @by_name[name.to_s]
end