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

A disequality is not representable on the wire, and Ruby would otherwise derive it from == and silently answer false for every row.

Raises:



146
147
148
149
150
151
# File 'lib/quicopt/model.rb', line 146

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

  raise UnsupportedExpression,
        "!= is not a constraint the wire schema can carry; model it with a binary and a big-M row"
end

#*(other) ⇒ Expression

Returns self * other.

Returns:

Raises:



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

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

#**(exponent) ⇒ Expression

Integer powers 0, 1 and 2 only — the degree the subset admits.

Returns:

Raises:



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

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 outside the linear/quadratic subset (0, 1 or 2)"
  end
end

#+(other) ⇒ Expression

Returns +self + other+.

Returns:



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

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

#+@Expression

Returns self, unchanged.

Returns:



114
115
116
# File 'lib/quicopt/model.rb', line 114

def +@
  to_expr
end

#-(other) ⇒ Expression

Returns self - other.

Returns:



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

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

#-@Expression

Returns the negation of self.

Returns:



109
110
111
# File 'lib/quicopt/model.rb', line 109

def -@
  to_expr.scale(-1.0)
end

#/(divisor) ⇒ Expression

Division by a numeric constant only — dividing by an expression is not a polynomial and has no wire form.

Returns:

Raises:



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

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:



119
120
121
# File 'lib/quicopt/model.rb', line 119

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:



136
137
138
139
140
# File 'lib/quicopt/model.rb', line 136

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

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

#>=(other) ⇒ Constraint

Returns the row self >= other.

Returns:



124
125
126
# File 'lib/quicopt/model.rb', line 124

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:



158
159
160
# File 'lib/quicopt/model.rb', line 158

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