Module: Quicopt::Wire
- Defined in:
- lib/quicopt/wire.rb
Overview
Node constructors over the generated codec, and the lowering from the DSL's normalised polynomials into the IR's expression trees.
Constant Summary collapse
- PB =
The generated wire schema —
Program,Expression,Constraint, … — as the client's IR. Aliased for brevity throughout the codec. Quicopt::Modeler::V1
Class Method Summary collapse
-
.apply(op, args) ⇒ Object
An operator application:
opapplied to child expressions. -
.const(value) ⇒ Object
A constant expression node.
-
.constraint(con) ⇒ PB::Constraint
One DSL constraint row → an IR function-in-set row.
-
.decode(bytes) ⇒ PB::Program
Parse wire bytes back into a
Program— the inverse ofencode, and the only sound way to compare two encodings. -
.encode(model) ⇒ String
Serialize a model (or an already-lowered
Program) to wire bytes. -
.expression(expr) ⇒ PB::Expression
A normalised DSL expression → an IR expression tree: the constant (when nonzero), then the linear terms, then the quadratic ones — the term order the sibling front-ends emit.
-
.number(value) ⇒ Object
Normalise negative zero on its way to the wire.
-
.program(model) ⇒ PB::Program
Lower
modelto the wire IR. -
.scaled(coef, expr) ⇒ Object
coef · expr, dropping a unit coefficient. -
.sum(terms) ⇒ Object
Fold terms into an n-ary sum, dropping additive-zero constants.
-
.var(name) ⇒ Object
A reference to a flat (unindexed) variable.
-
.var_decl(variable) ⇒ PB::VarDecl
A variable declaration →
VarDecl.
Class Method Details
.apply(op, args) ⇒ Object
An operator application: op applied to child expressions.
95 96 97 |
# File 'lib/quicopt/wire.rb', line 95 def apply(op, args) PB::Expression.new(apply: PB::Apply.new(op: op, args: args)) end |
.const(value) ⇒ Object
A constant expression node. Always a set oneof case, so Const(0.0)
survives the round-trip rather than vanishing as a proto3 default.
72 73 74 |
# File 'lib/quicopt/wire.rb', line 72 def const(value) PB::Expression.new(constant: number(value)) end |
.constraint(con) ⇒ PB::Constraint
One DSL constraint row → an IR function-in-set row.
134 135 136 137 138 139 140 141 142 143 |
# File 'lib/quicopt/wire.rb', line 134 def constraint(con) f = expression(con.body) c = con.bound case con.sense when :eq then PB::Constraint.new(f: minus(f, c), set: con_set(:zero)) when :ge then PB::Constraint.new(f: minus(f, c), set: con_set(:nonneg)) when :le then PB::Constraint.new(f: geq(c, f), set: con_set(:nonneg)) else raise Error, "unknown constraint sense #{con.sense.inspect}" end end |
.decode(bytes) ⇒ PB::Program
Parse wire bytes back into a Program — the inverse of encode, and the
only sound way to compare two encodings.
64 65 66 |
# File 'lib/quicopt/wire.rb', line 64 def decode(bytes) PB::Program.decode(bytes) end |
.encode(model) ⇒ String
Serialize a model (or an already-lowered Program) to wire bytes.
55 56 57 |
# File 'lib/quicopt/wire.rb', line 55 def encode(model) PB::Program.encode(model.is_a?(PB::Program) ? model : program(model)) end |
.expression(expr) ⇒ PB::Expression
A normalised DSL expression → an IR expression tree: the constant (when nonzero), then the linear terms, then the quadratic ones — the term order the sibling front-ends emit.
122 123 124 125 126 127 128 |
# File 'lib/quicopt/wire.rb', line 122 def expression(expr) terms = [] terms << const(expr.constant) unless expr.constant.zero? expr.linear.each { |v, c| terms << scaled(c, var(v.name)) } expr.quadratic.each { |(a, b), c| terms << scaled(c, apply("*", [var(a.name), var(b.name)])) } sum(terms) end |
.number(value) ⇒ Object
Normalise negative zero on its way to the wire.
IEEE keeps -0.0 distinct from 0.0 — a distinct bit pattern, and a
distinct protobuf value — and moving a bound across an inequality
(+f ≤ 0+ ⇒ 0 − f ≥ 0) produces one whenever that bound is zero. The
sibling clients emit 0.0 there, so collapse the sign here rather than
shipping bytes that differ from theirs by a sign bit.
83 84 85 86 |
# File 'lib/quicopt/wire.rb', line 83 def number(value) float = value.to_f float.zero? ? 0.0 : float end |
.program(model) ⇒ PB::Program
Lower model to the wire IR.
42 43 44 45 46 47 48 49 |
# File 'lib/quicopt/wire.rb', line 42 def program(model) PB::Program.new( vars: model.variables.map { |v| var_decl(v) }, objective: expression(model.objective), sense: model.sense, constraints: model.constraints.map { |c| constraint(c) } ) end |
.scaled(coef, expr) ⇒ Object
coef · expr, dropping a unit coefficient.
100 101 102 |
# File 'lib/quicopt/wire.rb', line 100 def scaled(coef, expr) coef == 1.0 ? expr : apply("*", [const(coef), expr]) end |
.sum(terms) ⇒ Object
Fold terms into an n-ary sum, dropping additive-zero constants. No terms is the zero constant; one term is itself.
106 107 108 109 110 111 112 |
# File 'lib/quicopt/wire.rb', line 106 def sum(terms) surviving = terms.reject { |t| t.node == :constant && t.constant.zero? } return const(0.0) if surviving.empty? return surviving.first if surviving.size == 1 apply("+", surviving) end |
.var(name) ⇒ Object
A reference to a flat (unindexed) variable. The Index message is emitted
present-but-empty, matching the reference codec.
90 91 92 |
# File 'lib/quicopt/wire.rb', line 90 def var(name) PB::Expression.new(var: PB::VarRef.new(name: name, index: PB::Index.new)) end |
.var_decl(variable) ⇒ PB::VarDecl
A variable declaration → VarDecl. Bounds are scalars (a Param-table
bound needs indexed variables, which the DSL does not have); ±Inf passes
straight through as a free direction.
151 152 153 154 155 156 157 |
# File 'lib/quicopt/wire.rb', line 151 def var_decl(variable) PB::VarDecl.new(name: variable.name, domain: variable.domain, lower: PB::Bound.new(scalar: number(variable.lower)), upper: PB::Bound.new(scalar: number(variable.upper)), start: number(variable.start)) end |