Class: Quicopt::Expression
- Inherits:
-
Object
- Object
- Quicopt::Expression
- Includes:
- Operators
- Defined in:
- lib/quicopt/model.rb
Overview
A polynomial of degree ≤ 2 over a model's variables, always kept in collected form: constant + Σ cᵢ·xᵢ + Σ qᵢⱼ·xᵢxⱼ.
A value read from a permutation (route.item_at(0)) or a lookup
table (dist[i, j]) counts as one factor, like a variable:
2 * dist[i, j] is linear, x * dist[i, j] quadratic.
Instances are immutable — every operator returns a new expression — so an expression can be shared, reused and stored without aliasing surprises.
Instance Attribute Summary collapse
-
#constant ⇒ Float
readonly
The constant term.
-
#linear ⇒ Hash{Variable=>Float}
readonly
Linear coefficients, in insertion order.
-
#quadratic ⇒ Hash{Array(Variable,Variable)=>Float}
readonly
Quadratic coefficients, keyed by the factor pair in model order, in insertion order.
Class Method Summary collapse
-
.cast(value) ⇒ Object
Coerce a number, variable or expression into an
Expression. -
.castable?(value) ⇒ Boolean
Whether
valuecan become an expression. -
.merge(left, right) ⇒ Object
private
Sum two coefficient maps, dropping terms that cancel exactly.
-
.pair(left, right) ⇒ Object
private
The canonical key for a quadratic term: the two factors in model order, so
x*yandy*xaccumulate on one coefficient. -
.prune(terms) ⇒ Object
private
Drop exactly-zero coefficients, so a cancelled term leaves no trace on the wire.
Instance Method Summary collapse
-
#constant? ⇒ Boolean
Whether this is a bare constant.
-
#degree ⇒ Integer
0, 1 or 2 — the highest degree present.
-
#eq?(other) ⇒ Boolean
Value equality — the same constant and the same coefficient maps.
-
#factors ⇒ Array<Variable, Placement, TableEntry, Condition, Extremum>
private
Every factor of every term — variables and reads of permutations and lookup tables — in first-use order.
-
#initialize(constant: 0.0, linear: {}, quadratic: {}) ⇒ Expression
constructor
A new instance of Expression.
-
#inspect ⇒ String
The polynomial, tagged with the class.
-
#plus(other) ⇒ Expression
self + other.
-
#scale(factor) ⇒ Expression
selfscaled by the numericfactor. -
#times(other) ⇒ Expression
Multiply two expressions, staying at degree 2 or below.
-
#to_expr ⇒ Expression
Self.
-
#to_s ⇒ Object
Renders the polynomial, e.g.
-
#variables ⇒ Array<Variable>
Every variable this expression mentions, in first-use order — including those in the positions of a lookup table read.
Methods included from Operators
#!=, #*, #**, #+, #+@, #-, #-@, #/, #<, #<=, #==, #>, #>=, #coerce
Constructor Details
#initialize(constant: 0.0, linear: {}, quadratic: {}) ⇒ Expression
Returns a new instance of Expression.
297 298 299 300 301 302 |
# File 'lib/quicopt/model.rb', line 297 def initialize(constant: 0.0, linear: {}, quadratic: {}) @constant = constant.to_f @linear = linear.freeze @quadratic = quadratic.freeze freeze end |
Instance Attribute Details
#constant ⇒ Float (readonly)
Returns the constant term.
289 290 291 |
# File 'lib/quicopt/model.rb', line 289 def constant @constant end |
#linear ⇒ Hash{Variable=>Float} (readonly)
Returns linear coefficients, in insertion order. A read of a permutation or lookup table is a key here too.
292 293 294 |
# File 'lib/quicopt/model.rb', line 292 def linear @linear end |
Class Method Details
.cast(value) ⇒ Object
Coerce a number, variable or expression into an Expression.
312 313 314 315 316 317 318 319 320 |
# File 'lib/quicopt/model.rb', line 312 def self.cast(value) case value when Expression then value when Variable then value.to_expr when Numeric then new(constant: value) else raise TypeError, "cannot use #{value.class} in a Quicopt expression" end end |
.castable?(value) ⇒ Boolean
Returns whether value can become an expression.
305 306 307 |
# File 'lib/quicopt/model.rb', line 305 def self.castable?(value) value.is_a?(Expression) || value.is_a?(Variable) || value.is_a?(Numeric) end |
.merge(left, right) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Sum two coefficient maps, dropping terms that cancel exactly.
432 433 434 435 436 437 438 439 |
# File 'lib/quicopt/model.rb', line 432 def self.merge(left, right) return left.dup if right.empty? return right.dup if left.empty? out = left.dup right.each { |k, c| out[k] = (out[k] || 0.0) + c } prune(out) end |
.pair(left, right) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
The canonical key for a quadratic term: the two factors in model order, so
x*y and y*x accumulate on one coefficient.
453 454 455 |
# File 'lib/quicopt/model.rb', line 453 def self.pair(left, right) ((left.order <=> right.order) <= 0 ? [left, right] : [right, left]).freeze end |
.prune(terms) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Drop exactly-zero coefficients, so a cancelled term leaves no trace on the wire.
445 446 447 |
# File 'lib/quicopt/model.rb', line 445 def self.prune(terms) terms.reject { |_, c| c.zero? } end |
Instance Method Details
#constant? ⇒ Boolean
Returns whether this is a bare constant.
336 337 338 |
# File 'lib/quicopt/model.rb', line 336 def constant? degree.zero? end |
#degree ⇒ Integer
Returns 0, 1 or 2 — the highest degree present.
328 329 330 331 332 333 |
# File 'lib/quicopt/model.rb', line 328 def degree return 2 unless @quadratic.empty? return 1 unless @linear.empty? 0 end |
#eq?(other) ⇒ Boolean
Returns value equality — the same constant and the same
coefficient maps. Named eq? because == builds a constraint row.
424 425 426 427 |
# File 'lib/quicopt/model.rb', line 424 def eq?(other) other.is_a?(Expression) && @constant == other.constant && @linear == other.linear && @quadratic == other.quadratic end |
#factors ⇒ Array<Variable, Placement, TableEntry, Condition, Extremum>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Every factor of every term — variables and reads of permutations and lookup tables — in first-use order.
400 401 402 403 404 |
# File 'lib/quicopt/model.rb', line 400 def factors seen = @linear.keys @quadratic.each_key { |a, b| seen |= [a, b] } seen end |
#inspect ⇒ String
Returns the polynomial, tagged with the class.
418 419 420 |
# File 'lib/quicopt/model.rb', line 418 def inspect "#<Quicopt::Expression #{self}>" end |
#plus(other) ⇒ Expression
Returns self + other.
341 342 343 344 345 |
# File 'lib/quicopt/model.rb', line 341 def plus(other) Expression.new(constant: @constant + other.constant, linear: Expression.merge(@linear, other.linear), quadratic: Expression.merge(@quadratic, other.quadratic)) end |
#scale(factor) ⇒ Expression
Returns self scaled by the numeric factor.
348 349 350 351 352 353 354 355 |
# File 'lib/quicopt/model.rb', line 348 def scale(factor) f = factor.to_f return Expression.new if f.zero? Expression.new(constant: @constant * f, linear: @linear.transform_values { |c| c * f }, quadratic: @quadratic.transform_values { |c| c * f }) end |
#times(other) ⇒ Expression
Multiply two expressions, staying at degree 2 or below.
A constant factor just scales; two linear factors expand into quadratic terms. Anything else would exceed degree 2 and raises instead.
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
# File 'lib/quicopt/model.rb', line 364 def times(other) return scale(other.constant) if other.constant? return other.scale(@constant) if constant? if degree > 1 || other.degree > 1 raise UnsupportedExpression, "multiplying degree #{degree} by degree #{other.degree} gives a cubic or " \ "higher term; a Quicopt model is linear or quadratic" end quadratic = {} @linear.each do |va, ca| other.linear.each do |vb, cb| key = Expression.pair(va, vb) quadratic[key] = (quadratic[key] || 0.0) + (ca * cb) end end Expression.new(constant: @constant * other.constant, linear: Expression.merge(@linear.transform_values { |c| c * other.constant }, other.linear.transform_values { |c| c * @constant }), quadratic: Expression.prune(quadratic)) end |
#to_expr ⇒ Expression
Returns self.
323 324 325 |
# File 'lib/quicopt/model.rb', line 323 def to_expr self end |
#to_s ⇒ Object
Renders the polynomial, e.g. -4 + 3*x + 2*y.
407 408 409 410 411 412 413 414 415 |
# File 'lib/quicopt/model.rb', line 407 def to_s terms = [] terms << Format.number(@constant) unless @constant.zero? @linear.each { |v, c| terms << Format.term(c, v.to_s) } @quadratic.each { |(a, b), c| terms << Format.term(c, "#{a}*#{b}") } return "0" if terms.empty? terms.join(" + ").gsub(" + -", " - ") end |
#variables ⇒ Array<Variable>
Every variable this expression mentions, in first-use order — including those in the positions of a lookup table read.
391 392 393 |
# File 'lib/quicopt/model.rb', line 391 def variables factors.flat_map(&:variables).uniq end |