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)

The model is the authoring surface; to_program / encode lower it to the wire IR, and Quicopt::Client#solve ships it.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeModel

Returns a new instance of Model.



463
464
465
466
467
468
469
# File 'lib/quicopt/model.rb', line 463

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



457
458
459
# File 'lib/quicopt/model.rb', line 457

def constraints
  @constraints
end

#objectiveExpression (readonly)

Returns the objective (the zero expression until set).

Returns:

  • (Expression)

    the objective (the zero expression until set)



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

def objective
  @objective
end

#senseString (readonly)

Returns "min" or "max".

Returns:

  • (String)

    "min" or "max"



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

def sense
  @sense
end

#variablesArray<Variable> (readonly)

Returns the declared variables, in declaration order.

Returns:

  • (Array<Variable>)

    the declared variables, in declaration order



455
456
457
# File 'lib/quicopt/model.rb', line 455

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



531
532
533
534
535
536
537
538
539
# File 'lib/quicopt/model.rb', line 531

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:



500
501
502
# File 'lib/quicopt/model.rb', line 500

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

#encodeString

Lower this model and serialize it to wire bytes.

Returns:

  • (String)

    binary protobuf bytes



551
552
553
# File 'lib/quicopt/model.rb', line 551

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



567
568
569
# File 'lib/quicopt/model.rb', line 567

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 normalisation the other front-ends apply, so the same model reaches the service as the same IR whichever client authored it.

Returns:



490
491
492
493
494
495
# File 'lib/quicopt/model.rb', line 490

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:



521
522
523
# File 'lib/quicopt/model.rb', line 521

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

#minimize(expr) ⇒ Expression

Minimize expr.

Returns:



514
515
516
# File 'lib/quicopt/model.rb', line 514

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 wire name; auto-generated when omitted

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

    the starting point; defaults to 0 clamped into the bounds, matching the other clients' front-ends

Returns:



479
480
481
# File 'lib/quicopt/model.rb', line 479

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

#to_programQuicopt::Modeler::V1::Program

Lower this model to the wire IR.

Returns:

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


544
545
546
# File 'lib/quicopt/model.rb', line 544

def to_program
  Wire.program(self)
end

#to_sObject

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



556
557
558
559
560
561
562
563
564
# File 'lib/quicopt/model.rb', line 556

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 its wire name.

Returns:



507
508
509
# File 'lib/quicopt/model.rb', line 507

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