Skip to content

Commit d6cfb2f

Browse files
odowblegat
andauthored
Simplify div of polynomial denominator (#118)
Co-authored-by: Benoît Legat <[email protected]>
1 parent faf3389 commit d6cfb2f

File tree

2 files changed

+71
-8
lines changed

2 files changed

+71
-8
lines changed

src/nl_to_polynomial.jl

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,21 +94,38 @@ function _to_polynomial!(
9494
return a^b
9595
elseif _is_operator(expr, :/) && length(operands) == 2
9696
a, b = _to_polynomial!.(Ref(d), T, operands)
97-
return a / b
98-
# TODO(odow): see PR#113
99-
# divisor, remainder = Base.divrem(a, b)
100-
# if iszero(remainder)
101-
# return divisor
102-
# else
103-
# return a / b
104-
# end
97+
return _checked_div(a, b)
10598
elseif _is_variable(expr)
10699
return _to_polynomial!(d, T, operands[1])
107100
else
108101
throw(InvalidNLExpression("Cannot convert `$(expr)` into a polynomial"))
109102
end
110103
end
111104

105+
_checked_div(a, b) = a / b
106+
107+
function _checked_div(α, p::MP.AbstractPolynomialLike)
108+
if !MP.isconstant(p)
109+
throw(InvalidNLExpression("Cannot convert `α / ($p)` to a polynomial."))
110+
end
111+
return MP.constant_term/ MP.convert_to_constant(p), p)
112+
end
113+
114+
function _checked_div(
115+
a::MP.AbstractPolynomialLike,
116+
b::MP.AbstractPolynomialLike,
117+
)
118+
divisor, remainder = Base.divrem(a, b)
119+
if !iszero(remainder)
120+
throw(
121+
InvalidNLExpression(
122+
"Cannot convert `($(a)) / ($b)` into a polynomial as the Euclidean division has a nonzero remainder `$remainder` (the divisor is `$divisor`).",
123+
),
124+
)
125+
end
126+
return divisor
127+
end
128+
112129
function _to_polynomial(expr, ::Type{T}) where {T}
113130
d = Dict{MOI.VariableIndex,VarType}()
114131
poly = _to_polynomial!(d, T, expr)

test/qcqp.jl

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,52 @@ function test_scalar_nonlinear_function(x, y, T)
215215
return
216216
end
217217

218+
function test_scalar_nonlinear_function_div_rem_zero(x, y, T)
219+
inner = Model{T}()
220+
model = PolyJuMP.JuMP.GenericModel{T}() do
221+
return PolyJuMP.QCQP.Optimizer{T}(MOI.Utilities.MockOptimizer(inner))
222+
end
223+
PolyJuMP.@variable(model, x)
224+
PolyJuMP.@objective(model, Min, x^3 / x)
225+
PolyJuMP.optimize!(model)
226+
@test MOI.get(inner, MOI.NumberOfVariables()) == 1
227+
@test isempty(MOI.get(inner, MOI.ListOfConstraintTypesPresent()))
228+
@test PolyJuMP.objective_function(model) isa PolyJuMP.GenericNonlinearExpr
229+
F = MOI.get(inner, MOI.ObjectiveFunctionType())
230+
@test F <: MOI.ScalarQuadraticFunction{T}
231+
return
232+
end
233+
234+
function test_scalar_nonlinear_function_div_rem_err(x, y, T)
235+
inner = Model{T}()
236+
model = PolyJuMP.JuMP.GenericModel{T}() do
237+
return PolyJuMP.QCQP.Optimizer{T}(MOI.Utilities.MockOptimizer(inner))
238+
end
239+
PolyJuMP.@variable(model, x)
240+
PolyJuMP.@variable(model, y)
241+
PolyJuMP.@objective(model, Min, x^3 / y)
242+
@test_throws PolyJuMP.InvalidNLExpression PolyJuMP.optimize!(model)
243+
PolyJuMP.@objective(model, Min, 2 / y)
244+
@test_throws PolyJuMP.InvalidNLExpression PolyJuMP.optimize!(model)
245+
PolyJuMP.@objective(model, Min, 2 / (x * y^2 - y^2 * x - 1))
246+
PolyJuMP.optimize!(model)
247+
return
248+
end
249+
250+
function test_scalar_nonlinear_function_div_rem_number(x, y, T)
251+
inner = Model{T}()
252+
model = PolyJuMP.JuMP.GenericModel{T}() do
253+
return PolyJuMP.QCQP.Optimizer{T}(MOI.Utilities.MockOptimizer(inner))
254+
end
255+
PolyJuMP.@variable(model, x)
256+
PolyJuMP.@objective(model, Min, 4x^3 / 2)
257+
PolyJuMP.optimize!(model)
258+
@test MOI.get(inner, MOI.NumberOfVariables()) == 2
259+
F, S = MOI.ScalarQuadraticFunction{T}, MOI.EqualTo{T}
260+
@test (F, S) in MOI.get(inner, MOI.ListOfConstraintTypesPresent())
261+
return
262+
end
263+
218264
function test_variable_primal(x, y, T)
219265
inner = Model{T}()
220266
optimizer = MOI.Utilities.MockOptimizer(inner)

0 commit comments

Comments
 (0)