Skip to content

Commit 58a707f

Browse files
zerozero
authored andcommitted
test: cover PreserveDiffModel cleanup + parameter getter paths
1 parent a724544 commit 58a707f

1 file changed

Lines changed: 73 additions & 0 deletions

File tree

test/nlp_program.jl

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,79 @@ function test_preserve_diff_model_structural_change_falls_back()
12521252
return
12531253
end
12541254

1255+
# A model feasible for small p and INFEASIBLE for large p via a Parameter-only change
1256+
# (x ∈ [0,1], x ≥ p is infeasible once p > 1): lets a preserved re-solve reach a
1257+
# non-differentiable status without any structural edit.
1258+
function _build_boundable_preserve_model()
1259+
model = DiffOpt.nonlinear_diff_model(Ipopt.Optimizer)
1260+
set_silent(model)
1261+
@variable(model, p in MOI.Parameter(0.5))
1262+
@variable(model, 0.0 <= x <= 1.0)
1263+
@constraint(model, x >= p)
1264+
@objective(model, Min, (x - 0.3)^2)
1265+
return model, p, x
1266+
end
1267+
1268+
function _reverse_dp_scalar(model, p, x, seed)
1269+
DiffOpt.empty_input_sensitivities!(model)
1270+
DiffOpt.set_reverse_variable(model, x, seed)
1271+
DiffOpt.reverse_differentiate!(model)
1272+
return MOI.get(model, DiffOpt.ReverseConstraintSet(), ParameterRef(p)).value
1273+
end
1274+
1275+
function test_preserve_diff_model_nondifferentiable_solve_discards()
1276+
# Covers the MOI.optimize! fallback (moi_wrapper.jl): a preserved Parameter-only
1277+
# re-solve that ends non-differentiable must DISCARD the diff model, and the next
1278+
# feasible solve must rebuild gradients identical to a fresh model.
1279+
model, p, x = _build_boundable_preserve_model()
1280+
MOI.set(model, DiffOpt.PreserveDiffModel(), true)
1281+
optimize!(model)
1282+
@assert is_solved_and_feasible(model)
1283+
_reverse_dp_scalar(model, p, x, 1.0) # instantiate the preserved diff model
1284+
diffopt = JuMP.unsafe_backend(model)
1285+
@test diffopt.diff !== nothing
1286+
set_parameter_value(p, 2.0) # Parameter-only push into the infeasible region
1287+
optimize!(model)
1288+
@test !is_solved_and_feasible(model)
1289+
@test diffopt.diff === nothing # the non-differentiable fallback fired
1290+
set_parameter_value(p, 0.5)
1291+
optimize!(model)
1292+
@assert is_solved_and_feasible(model)
1293+
dp = _reverse_dp_scalar(model, p, x, 1.0)
1294+
fresh, pf, xf = _build_boundable_preserve_model()
1295+
optimize!(fresh)
1296+
dp_fresh = _reverse_dp_scalar(fresh, pf, xf, 1.0)
1297+
@test isapprox(dp, dp_fresh; rtol = 1e-10)
1298+
return
1299+
end
1300+
1301+
function test_preserve_diff_model_parameter_set_getter_roundtrips()
1302+
# Covers MOI.get(::NonLinearProgram.Model, ::ConstraintSet, ::Parameter): after a
1303+
# preserved re-solve refreshes the value into the diff model, reading it back through
1304+
# the diff model's own getter must return that value.
1305+
model, p, x = _build_boundable_preserve_model()
1306+
MOI.set(model, DiffOpt.PreserveDiffModel(), true)
1307+
optimize!(model)
1308+
_reverse_dp_scalar(model, p, x, 1.0) # instantiate the preserved diff model
1309+
newval = 0.8
1310+
set_parameter_value(p, newval)
1311+
optimize!(model)
1312+
_reverse_dp_scalar(model, p, x, 1.0) # _refresh_parameters writes newval into diff
1313+
diffopt = JuMP.unsafe_backend(model)
1314+
ci_src = only(
1315+
MOI.get(
1316+
diffopt.optimizer,
1317+
MOI.ListOfConstraintIndices{
1318+
MOI.VariableIndex,
1319+
MOI.Parameter{Float64},
1320+
}(),
1321+
),
1322+
)
1323+
got = MOI.get(diffopt.diff, MOI.ConstraintSet(), diffopt.index_map[ci_src])
1324+
@test got.value == newval
1325+
return
1326+
end
1327+
12551328
end # module
12561329

12571330
TestNLPProgram.runtests()

0 commit comments

Comments
 (0)