Module: Quicopt::Operators

Included in:
Expression, Variable
Defined in:
lib/quicopt/model.rb

Overview

The operator surface shared by Variable and Expression.

Every operator is written against to_expr, so a variable and an expression behave identically. coerce makes a numeric on the left work too, which is what lets 3 * x parse: Ruby hands the multiplication back to us rather than failing in Integer#*.

Instance Method Summary collapse

Instance Method Details

#!=(other) ⇒ Object

Quicopt has no "not equal to" constraint, and Ruby would otherwise derive != from == and silently answer false for every row.

Raises:



148
149
150
151
152
153
154
# File 'lib/quicopt/model.rb', line 148

def !=(other)
  return !equal?(other) unless Expression.castable?(other)

  raise UnsupportedExpression,
        "!= is not a constraint Quicopt can express; model it with a binary " \
        "variable and two big-M rows"
end

#*(other) ⇒ Expression

Returns self * other.

Returns:

Raises:



75
76
77
# File 'lib/quicopt/model.rb', line 75

def *(other)
  to_expr.times(Expression.cast(other))
end

#**(exponent) ⇒ Expression

Integer powers 0, 1 and 2 only — a Quicopt model is linear or quadratic.

Returns:

Raises:



98
99
100
101
102
103
104
105
106
107
108
# File 'lib/quicopt/model.rb', line 98

def **(exponent)
  case exponent
  when 0 then Expression.new(constant: 1.0)
  when 1 then to_expr
  when 2 then to_expr.times(to_expr)
  else
    raise UnsupportedExpression,
          "exponent #{exponent.inspect} is not supported: a Quicopt model is " \
          "linear or quadratic, so the exponent must be 0, 1 or 2"
  end
end

#+(other) ⇒ Expression

Returns +self + other+.

Returns:



64
65
66
# File 'lib/quicopt/model.rb', line 64

def +(other)
  to_expr.plus(Expression.cast(other))
end

#+@Expression

Returns self, unchanged.

Returns:



116
117
118
# File 'lib/quicopt/model.rb', line 116

def +@
  to_expr
end

#-(other) ⇒ Expression

Returns self - other.

Returns:



69
70
71
# File 'lib/quicopt/model.rb', line 69

def -(other)
  to_expr.plus(Expression.cast(other).scale(-1.0))
end

#-@Expression

Returns the negation of self.

Returns:



111
112
113
# File 'lib/quicopt/model.rb', line 111

def -@
  to_expr.scale(-1.0)
end

#/(divisor) ⇒ Expression

Division by a numeric constant only — dividing by an expression is not a polynomial, and a Quicopt model is a polynomial.

Returns:

Raises:



84
85
86
87
88
89
90
91
92
# File 'lib/quicopt/model.rb', line 84

def /(divisor)
  unless divisor.is_a?(Numeric)
    raise UnsupportedExpression,
          "cannot divide by #{divisor.class}: only division by a numeric constant is supported"
  end
  raise ZeroDivisionError, "divided by 0" if divisor.zero?

  to_expr.scale(1.0 / divisor)
end

#<=(other) ⇒ Constraint

Returns the row self <= other.

Returns:



121
122
123
# File 'lib/quicopt/model.rb', line 121

def <=(other)
  Constraint.build(self, other, :le)
end

#==(other) ⇒ Constraint, Boolean

Builds an equality row, not a boolean — x == 5 is a constraint.

A non-numeric, non-expression operand falls back to identity, so an incidental comparison (+x == nil+, x == "x") still answers false instead of raising. Hash and Set membership are unaffected either way: they key off +hash+/+eql?+, which Variable defines separately.

Returns:



138
139
140
141
142
# File 'lib/quicopt/model.rb', line 138

def ==(other)
  return equal?(other) unless Expression.castable?(other)

  Constraint.build(self, other, :eq)
end

#>=(other) ⇒ Constraint

Returns the row self >= other.

Returns:



126
127
128
# File 'lib/quicopt/model.rb', line 126

def >=(other)
  Constraint.build(self, other, :ge)
end

#coerce(other) ⇒ Array(Expression, Expression)

Lets Ruby fold a numeric on the left into an expression, so 3 * x and 10 - x dispatch here instead of failing inside +Integer+/+Float+.

Parameters:

  • other (Numeric)

Returns:



161
162
163
# File 'lib/quicopt/model.rb', line 161

def coerce(other)
  [Expression.cast(other), to_expr]
end