Expand description
Options registered for ipopt.opt compatibility whose feature
pounce does not implement (gh#483 follow-up, continuing #191).
§Why these are refused rather than ignored
upstream_options.rs is a faithful port of Ipopt’s option registry:
every name Ipopt registers is registered here, so an ipopt.opt
written for Ipopt parses unchanged. That is a real compatibility
benefit — and it silently turned ~200 knobs into no-ops, because
registering an option says nothing about implementing it. Setting one
did exactly nothing and said exactly nothing.
Issue #191 audited this class and fixed the half where the feature runs and only the option’s read site was missing. It explicitly scoped out “feature genuinely unimplemented — expected no-ops”. This module closes that half: an option naming a feature pounce does not have is now an error, not a shrug.
§What is and is not in the table
Membership was established per option, not guessed:
- the option’s name appears in no crate source outside the
registry (whole-word —
penalty_maxdoes not count as present becausel1_penalty_maxexists), and - the feature it configures is absent too.
Both are needed. An option whose name is unread but whose feature
runs — the limited_memory_* tail — is a missing read site, not a
missing feature; refusing those would fail solves whose current
answers are already correct. They are deliberately not here;
wiring them is the other half of the work.
Rule 2 is the one that takes work, and the corrector knobs are why.
corrector_type and its three safeguards were once listed here as an
example of rule 1 alone — “the Mehrotra corrector runs, so these need
read sites” — on the strength of pounce having a corrector at all.
They configure a different one. The registry files them under
FilterLSAcceptor::RegisterOptions, and what they select is
FilterLSAcceptor::TryCorrector: a corrector step tried inside the
line search and accepted only if complementarity does not grow by
more than corrector_compl_avrg_red_fact. pounce’s corrector is
Mehrotra’s, in the search-direction right-hand side, reached through
mehrotra_algorithm — an option upstream registers separately and
pounce reads. No acceptor here takes a corrector trial, so the four
are refused (#551 / #677). A dev-note had already measured the
consequence without naming it: corrector_type=affine on robot_a
was recorded as “identical to plain adaptive”.
A third shape belongs to neither rule: an option for a
sub-capability of a feature that does run.
resto_failure_feasibility_threshold is the example — restoration
runs, but nothing reclassifies a stopped restoration as a failure
below a feasibility threshold, so honouring it means building the
capability, not adding a line. Those entries are refused too, and
their advice says what the parent feature does instead, so the
message tells a user which of the two they hit. max_resto_iter
looked like this shape and was not: RestoConvCheckAdapter has
capped successive restoration iterations all along, under the name
maximum_resto_iters. It is wired, not refused.
The clearest case is the penalty line search. pounce implements
IpPenaltyLSAcceptor (line_search_method=penalty), so its knobs
(nu_init, nu_inc, rho, eta_penalty) are read sites to add.
Ipopt’s other penalty acceptor — the CG-penalty / inexact-Newton
one — has no counterpart here at all, and the port registered its
whole option set. Those are refused.
An entry leaves the table by being implemented. option_file_name
was here — refusing it was the cheap half of gh#518’s “implement it
or fail loudly” — until gh#518 got the other half:
crate::application::IpoptApplication::initialize_with_option_file
reads the named file, so the option now configures something.
§Backend knobs warn, they do not refuse
The 111 ma27_* / ma77_* / ma86_* / ma97_* / mumps_* /
pardiso_* / pardisomkl_* / spral_* / wsmp_* options, plus
pardisolib, tune linear-solver backends pounce does not ship at
all: it factors the KKT system with feral or with MA57. They were
silent — #551 section 2 — and they are the one class where the
refusal above is the wrong instrument.
Refusing them attacks the goal the registry exists to serve. The
rest of this table refuses an option whose feature the caller is
plainly asking for; ma97_order in a portable ipopt.opt is
usually not that. Such a file routinely carries settings for several
backends at once so that one file runs everywhere, and pounce would
reject it wholesale over knobs the run never touches — a hard error
for a user who is not using MA97 and never asked pounce to. That is
strictly worse than the silence: it breaks a working file instead of
under-serving one.
But silence is what #677 cost, so the answer is the third
disposition this module already carries. The precedent is
UNEXPLOITED_HINTS — pinned by a_caching_hint_warns_but_solves
in pounce-cli/tests/unimplemented_options.rs — where the project
chose WARN over REFUSE for exactly this trade: ignoring the option
costs the caller nothing they cannot see, so blocking the solve
would take more from them than the silence did. Backend knobs are a
stronger case for it than the hints are: a hint changes the
evaluation count, whereas a knob for a backend that is not linked
could not have changed anything even in principle.
Three properties follow from that, and each is pinned by a test:
- Only when explicitly set to a non-default. The same gate as
everything else here (below). A file spelling out
ma97_u 1e-8asks for nothing, and a default run must stay completely silent —a_default_run_is_silent. - One line per backend family, not per option. An MA97-tuned
file sets a dozen
ma97_*knobs; a dozen near-identical lines is noise a reader learns to skip, which is silence with extra steps. The warning names the backend, lists the options it saw, and says the rest of the family is inert too. - The solve runs and its answer is unaffected, which the warning says in as many words — otherwise a warning naming a linear solver reads as “your factorization may be wrong”.
hsllib stays in the refusal table above rather than moving here,
and the line is deliberate: pounce has an HSL backend (MA57), so
hsllib is a caller trying to reach a solver pounce can actually
run, by a mechanism it does not have — the refusal tells them to
build with --features ma57 instead of letting them believe MA57 is
loaded. pardisolib has no such other route (there is no Pardiso
here by any means), so it warns with the rest of its family.
§…unless they are all there is
The rule above rests on one premise, stated in it: the file has
other business here, and failing the run over ma97_order would
reject something the caller wanted for the sake of a knob it never
touches. When the backend knobs are all that is there, the premise
is gone. Nothing in the file survives, so there is no working run
left to protect, and warning-then-solving answers “tune the linear
solver” by tuning nothing and reporting success — the shape of
gh#677, not a fix for it.
So backend_only_refusal refuses that one case. It is the
boundary of the warn rule rather than an exception to it: every file
the portability argument was ever about still warns and still
solves, because every such file has something else in it.
Note that a file which selects the backend it tunes never reaches
here — linear_solver=ma97 is refused earlier, by
crate::application::IpoptApplication::unimplemented_linear_solver.
What this catches is the file that tunes MA97 without ever saying
so, which is the case that used to run FERAL and report success.
§The default gate
Only an explicit value different from the registered default is
refused. corrector_type left alone, or an ipopt.opt that spells
out defaults, must keep working: those ask for nothing. Refusing them
would break the very compatibility the registry exists to provide.
This gate binds backend_only_refusal too: a file of nothing but
backend knobs at their registered defaults is silent, not refused.
Structs§
- Unimplemented
Backend - One linear-solver backend pounce does not implement, and the registered options that tune it.
- Unimplemented
Feature - One unimplemented feature and the options that configure it.
- Unimplemented
Value - One registered value of a string option that pounce does not implement, even though the option itself is read and other values of it work.
Constants§
- CONVEX_
UNEXPLOITED_ HINTS - The same four hints again — on the convex route, where they are still unexploited.
- UNEXPLOITED_
HINTS - Options that are honored in the sense that matters — the answer is unaffected — but whose performance hint pounce does not exploit. These warn rather than fail: refusing them would stop a solve that returns the right result today, only a little slower.
- UNIMPLEMENTED_
BACKENDS - Every linear-solver backend pounce does not implement, with the options that tune it. Warned about when set; never refused.
- UNIMPLEMENTED_
FEATURES - Feature groups pounce does not implement. Refused when set.
- UNIMPLEMENTED_
VALUES - String-option values pounce does not implement. Refused when set.
Functions§
- backend_
only_ refusal - The refusal for a run whose options configure nothing but backends
pounce does not ship, or
None. - backend_
warnings - Warnings for backend knobs the caller set. Never blocks a solve, and
emits at most one line per backend family: an
ipopt.opttuned for MA97 sets a dozenma97_*knobs at once, and a dozen near-identical lines would be noise the reader learns to skip. - convex_
hint_ warnings - Warnings for the constant-derivative hints on the convex route, where — unlike the NLP route — nothing reads them. Never blocks a solve. Call this only when the convex dispatch is actually taken; on the NLP route the same options are honoured, and warning there would contradict the reuse the solver really does.
- hint_
warnings - Warnings for hints pounce does not exploit. Never blocks a solve.
- refusal
- The first unimplemented-feature option the caller set, with the
message it earns.
Nonewhen nothing in the table was touched. - value_
refusal - The first unimplemented value the caller selected, with the message
it earns.
Nonewhen every string option holds a mode pounce runs.