| name | review-purejump |
| description | Review a src/PureJuMP/<name>.jl file for correctness, signature conventions, n-adjustment, start-value coverage, and cross-compatibility with the ADNLPProblems sibling. Reports findings as Errors (test-suite failures), Warnings (likely issues requiring judgment), and Info (notable but valid observations). |
| argument-hint | <problem-name> |
| allowed-tools | ["Read","Glob","Grep","Bash"] |
review-purejump
Audit a single src/PureJuMP/<name>.jl file against every rule enforced by the test suite, contributing guidelines, and module-loading conventions.
Arguments
The user invoked this skill with: $ARGUMENTS
This must be a problem name (e.g. hs100, arglina). Strip a leading src/PureJuMP/ prefix or trailing .jl suffix if present.
If $ARGUMENTS is empty after stripping, ask the user to provide a problem name (e.g. /review-purejump hs100) and stop — do not try to infer from the open file.
Instructions
Follow these steps in order.
Step 1 — Read the files
- Read
src/PureJuMP/<name>.jl in full.
- Glob-check whether the sibling files exist:
src/ADNLPProblems/<name>.jl
src/Meta/<name>.jl
Step 2 — Detect problem characteristics from the file text
Before running checks, determine the following from the file content. Do NOT read the sibling files.
- Is scalable? —
n is actually referenced in the function body beyond the signature (used to define variables, constraints, or expressions, not just forwarded).
- Has n-adjustment? — the body modifies
n before using it (e.g. n = 4 * div(n, 4), n = max(2, n)).
- Uses old @warn pattern? — contains
@warn(" (raw warn call) rather than @adjust_nvar_warn.
- Has nonlinear constraints? — any
@constraint (or @NLconstraint) with a nonlinear expression involving variables.
- Has linear constraints? — any
@constraint with a purely linear expression involving variables.
- Has variable bounds? — any
@variable with lb ≤ x ≤ ub syntax or scalar bound.
- Multi-function file? — more than one function name appears in the
export statement(s), e.g. export dixmaane, dixmaanf, ....
- Data-loading file? — calls
_ensure_data! or include("../../data/...").
- Has NLS objective? —
src/Meta/<name>.jl exists and contains :objtype => :least_squares.
Step 3 — Static analysis
Scan the text of src/PureJuMP/<name>.jl and collect findings under three severity levels.
Structural (→ Error)
Start values (→ Error)
Signature (→ Error)
n-adjustment (→ Error when test is affected, → Warning for soft cases)
Noteworthy (→ Info)
Step 4 — Dynamic analysis (run Julia)
Run the following Julia snippet via the Bash tool. Replace PROBLEM_NAME with the actual problem name.
Prerequisite: NLPModels, NLPModelsJuMP, and ADNLPModels must be available. If the snippet fails with a package-not-found error, install them first:
julia -e "import Pkg; Pkg.add([\"NLPModels\", \"NLPModelsJuMP\", \"ADNLPModels\"])"
julia --project -e "
using OptimizationProblems, NLPModels
import OptimizationProblems.PureJuMP, OptimizationProblems.ADNLPProblems
import NLPModelsJuMP, ADNLPModels
name = \"PROBLEM_NAME\"
if !(Symbol(name) in names(PureJuMP, all=false))
println(\"FAIL export: \", name, \" not exported from PureJuMP\")
exit(0)
end
ctor = PureJuMP.eval(Symbol(name))
println(\"OK export\")
# --- Instantiation ---
model = nothing
try
global model = ctor()
println(\"OK instantiate_model\")
catch e
println(\"FAIL instantiate_model: \", e)
end
model === nothing && exit(0)
nlp = nothing
try
global nlp = NLPModelsJuMP.MathOptNLPModel(model)
println(\"OK wrap_nlpmodel\")
catch e
println(\"FAIL wrap_nlpmodel: \", e)
end
nlp === nothing && exit(0)
# --- Objective at x0 ---
f0 = try obj(nlp, nlp.meta.x0) catch e; println(\"FAIL obj_at_x0: \", e); NaN end
if !isnan(f0)
println(isfinite(f0) ? \"OK obj_at_x0: \$(f0)\" : \"WARN obj_at_x0: non-finite value \$(f0)\")
end
# --- Meta consistency ---
has_meta = isdefined(OptimizationProblems, Symbol(name, :_meta))
if !has_meta
println(\"INFO no_meta: src/Meta/$(name).jl not loaded\")
else
meta = OptimizationProblems.eval(Symbol(name, :_meta))
get_pb_nvar = OptimizationProblems.eval(Symbol(:get_, name, :_nvar))
get_pb_ncon = OptimizationProblems.eval(Symbol(:get_, name, :_ncon))
get_pb_nlin = OptimizationProblems.eval(Symbol(:get_, name, :_nlin))
get_pb_nnln = OptimizationProblems.eval(Symbol(:get_, name, :_nnln))
get_pb_nequ = OptimizationProblems.eval(Symbol(:get_, name, :_nequ))
get_pb_nineq = OptimizationProblems.eval(Symbol(:get_, name, :_nineq))
actual_nequ = length(get_jfix(nlp))
checks = [
(\"meta_nvar\", meta[:nvar], nlp.meta.nvar,
meta[:nvar] == nlp.meta.nvar || meta[:variable_nvar]),
(\"meta_ncon\", meta[:ncon], nlp.meta.ncon,
meta[:ncon] == nlp.meta.ncon || meta[:variable_ncon]),
(\"minimize\", meta[:minimize], get_minimize(nlp),
meta[:minimize] == get_minimize(nlp)),
(\"has_bounds\", meta[:has_bounds], length(get_ifree(nlp)) < get_nvar(nlp),
meta[:has_bounds] == (length(get_ifree(nlp)) < get_nvar(nlp))),
(\"has_fixed_variables\", meta[:has_fixed_variables], get_ifix(nlp) != [],
meta[:has_fixed_variables] == (get_ifix(nlp) != [])),
(\"has_equalities_only\", meta[:has_equalities_only], actual_nequ == get_ncon(nlp) > 0,
meta[:has_equalities_only] == (actual_nequ == get_ncon(nlp) > 0)),
(\"has_inequalities_only\", meta[:has_inequalities_only], get_ncon(nlp) > 0 && actual_nequ == 0,
meta[:has_inequalities_only] == (get_ncon(nlp) > 0 && actual_nequ == 0)),
(\"getter_nvar\", get_pb_nvar(), nlp.meta.nvar,
get_pb_nvar() == nlp.meta.nvar || meta[:variable_nvar]),
(\"getter_ncon\", get_pb_ncon(), nlp.meta.ncon,
get_pb_ncon() == nlp.meta.ncon || meta[:variable_ncon]),
(\"getter_nlin\", get_pb_nlin(), nlp.meta.nlin,
get_pb_nlin() == nlp.meta.nlin || meta[:variable_ncon]),
(\"getter_nnln\", get_pb_nnln(), nlp.meta.nnln,
get_pb_nnln() == nlp.meta.nnln || meta[:variable_ncon]),
(\"getter_nequ\", get_pb_nequ(), actual_nequ,
get_pb_nequ() == actual_nequ || meta[:variable_ncon]),
(\"getter_nineq\", get_pb_nineq(), nlp.meta.ncon - actual_nequ,
get_pb_nineq() == (nlp.meta.ncon - actual_nequ) || meta[:variable_ncon]),
]
for (label, meta_val, actual_val, ok) in checks
println(ok ? \"OK\" : \"FAIL\", \" \", label, \": meta=\", meta_val, \" actual=\", actual_val)
end
println(\"DEFINED_EVERYWHERE_CHECK finite=\", isfinite(f0), \" meta=\", meta[:defined_everywhere])
# --- Scalability check ---
if meta[:variable_nvar]
n_test = 5
try
model2 = ctor(n = n_test)
nlp2 = NLPModelsJuMP.MathOptNLPModel(model2)
n_expected = get_pb_nvar(n = n_test)
ok = nlp2.meta.nvar == n_expected
println(ok ? \"OK scalable_nvar: \$(nlp2.meta.nvar)\" : \"FAIL scalable_nvar: got \$(nlp2.meta.nvar) expected \$(n_expected)\")
println(nlp2.meta.nvar != nlp.meta.nvar ? \"OK scalable_changes\" : \"WARN scalable_no_change: both n give nvar=\$(nlp.meta.nvar)\")
catch e
println(\"FAIL scalable_instantiate: \", e)
end
end
end
# --- Compatibility with ADNLPProblems ---
if Symbol(name) in names(ADNLPProblems, all=false)
ad_ctor = ADNLPProblems.eval(Symbol(name))
nlp_ad = nothing
try
global nlp_ad = ad_ctor()
println(\"OK ad_instantiate\")
catch e
println(\"WARN ad_instantiate: \", e)
end
if nlp_ad !== nothing
println(nlp.meta.nvar == nlp_ad.meta.nvar ? \"OK compat_nvar\" :
\"FAIL compat_nvar: jump=\$(nlp.meta.nvar) ad=\$(nlp_ad.meta.nvar)\")
println(nlp.meta.ncon == nlp_ad.meta.ncon ? \"OK compat_ncon\" :
\"FAIL compat_ncon: jump=\$(nlp.meta.ncon) ad=\$(nlp_ad.meta.ncon)\")
println(nlp.meta.x0 == nlp_ad.meta.x0 ? \"OK compat_x0\" :
\"FAIL compat_x0: starting points differ\")
println(nlp.meta.lvar == nlp_ad.meta.lvar ? \"OK compat_lvar\" :
\"FAIL compat_lvar: lower bounds differ\")
println(nlp.meta.uvar == nlp_ad.meta.uvar ? \"OK compat_uvar\" :
\"FAIL compat_uvar: upper bounds differ\")
x0 = nlp_ad.meta.x0
x1 = x0 .+ 0.01
f_jump = try obj(nlp, x0) catch; NaN end
f_ad = try obj(nlp_ad, x0) catch; NaN end
if !isnan(f_jump) && !isnan(f_ad)
n0 = max(abs(f_ad), 1.0)
println(isapprox(f_jump, f_ad, atol=1e-10*n0) ? \"OK compat_obj_x0\" :
\"FAIL compat_obj_x0: jump=\$(f_jump) ad=\$(f_ad)\")
f_jump1 = try obj(nlp, x1) catch; NaN end
f_ad1 = try obj(nlp_ad, x1) catch; NaN end
if !isnan(f_jump1) && !isnan(f_ad1)
n1 = max(abs(f_ad1), 1.0)
println(isapprox(f_jump1, f_ad1, atol=1e-10*n1) ? \"OK compat_obj_x1\" :
\"FAIL compat_obj_x1: jump=\$(f_jump1) ad=\$(f_ad1)\")
end
end
if nlp_ad.meta.ncon > 0
c_jump = try cons(nlp, x0) catch; nothing end
c_ad = try cons(nlp_ad, x0) catch; nothing end
if c_jump !== nothing && c_ad !== nothing
n0 = max(maximum(abs.(c_ad .+ 1e-30)), 1.0)
println(all(isapprox.(c_jump, c_ad, atol=1e-10*n0)) ? \"OK compat_cons_x0\" :
\"FAIL compat_cons_x0: constraint values differ at x0\")
c_jump1 = try cons(nlp, x1) catch; nothing end
c_ad1 = try cons(nlp_ad, x1) catch; nothing end
if c_jump1 !== nothing && c_ad1 !== nothing
n1 = max(maximum(abs.(c_ad1 .+ 1e-30)), 1.0)
println(all(isapprox.(c_jump1, c_ad1, atol=1e-10*n1)) ? \"OK compat_cons_x1\" :
\"FAIL compat_cons_x1: constraint values differ at x0+0.01\")
end
println(nlp.meta.lcon ≈ nlp_ad.meta.lcon ? \"OK compat_lcon\" :
\"FAIL compat_lcon: lower constraint bounds differ\")
println(nlp.meta.ucon ≈ nlp_ad.meta.ucon ? \"OK compat_ucon\" :
\"FAIL compat_ucon: upper constraint bounds differ\")
println(nlp.meta.lin == nlp_ad.meta.lin ? \"OK compat_lin\" :
\"FAIL compat_lin: linear constraint index sets differ\")
end
end
end
else
println(\"INFO no_ad_sibling\")
end
"
Interpret the output as follows:
| Line pattern | Action |
|---|
OK export | No finding |
FAIL export: ... | Error: function not exported from PureJuMP |
OK instantiate_model | No finding |
FAIL instantiate_model: ... | Error: JuMP model construction throws; all subsequent dynamic checks skipped |
OK wrap_nlpmodel | No finding |
FAIL wrap_nlpmodel: ... | Error: MathOptNLPModel wrapping fails |
OK obj_at_x0: X | No finding |
FAIL obj_at_x0: ... | Error: objective throws at starting point |
WARN obj_at_x0: non-finite value X | Warning: objective is Inf or -Inf at x0 |
INFO no_meta: ... | Info: src/Meta/<name>.jl not present; meta-consistency checks skipped |
OK <field>: meta=X actual=Y | No finding |
FAIL <field>: meta=X actual=Y | Error: meta field <field> claims X but the JuMP model gives Y |
DEFINED_EVERYWHERE_CHECK finite=false meta=true | Warning: objective is NaN/Inf at x0 but :defined_everywhere => true |
DEFINED_EVERYWHERE_CHECK finite=false meta=missing | Warning: objective is NaN/Inf at x0; :defined_everywhere should probably be false |
DEFINED_EVERYWHERE_CHECK finite=false meta=false | Info: consistent — domain is declared restricted and x0 confirms it |
DEFINED_EVERYWHERE_CHECK finite=true ... | No finding |
OK scalable_nvar: N | No finding |
FAIL scalable_nvar: got N expected M | Error: nvar at n=5 does not match get_<name>_nvar(n=5) |
OK scalable_changes | No finding |
WARN scalable_no_change: ... | Warning: meta says variable_nvar=true but both n values give the same nvar |
FAIL scalable_instantiate: ... | Error: model cannot be instantiated at small n |
OK ad_instantiate | No finding; note in report that compatibility was tested |
WARN ad_instantiate: ... | Warning: ADNLPProblems sibling exists but cannot be instantiated |
OK compat_nvar / FAIL compat_nvar: ... | Match/mismatch in number of variables between JuMP and AD models |
OK compat_ncon / FAIL compat_ncon: ... | Match/mismatch in number of constraints |
OK compat_x0 / FAIL compat_x0: ... | Error: starting points disagree — x0 in @variable start=... differs from AD x0 |
OK compat_lvar / FAIL compat_lvar: ... | Error: variable lower bounds disagree |
OK compat_uvar / FAIL compat_uvar: ... | Error: variable upper bounds disagree |
OK compat_obj_x0 / FAIL compat_obj_x0: ... | Error: objective values at x0 disagree |
OK compat_obj_x1 / FAIL compat_obj_x1: ... | Error: objective values at x0+0.01 disagree |
OK compat_cons_x0 / FAIL compat_cons_x0: ... | Error: constraint values at x0 disagree |
OK compat_cons_x1 / FAIL compat_cons_x1: ... | Error: constraint values at x0+0.01 disagree |
OK compat_lcon / FAIL compat_lcon: ... | Error: constraint lower bounds disagree |
OK compat_ucon / FAIL compat_ucon: ... | Error: constraint upper bounds disagree |
OK compat_lin / FAIL compat_lin: ... | Error: linear constraint index sets disagree |
INFO no_ad_sibling | Info: no ADNLPProblems sibling — JuMP-only problem; no compatibility checks run |
Step 5 — Report
Output:
## review-purejump: <name> [src/PureJuMP/<name>.jl]
Characteristics: <scalable|fixed-size>, <unconstrained|linear|nonlinear|mixed>, <with bounds|no bounds>
Dynamic checks: <passed | N failures | not available>
Compatibility: <tested against ADNLPProblems | JuMP-only problem | AD sibling not instantiatable>
### Errors (N)
- [E] <check>: <what was wrong and what was expected>
### Warnings (N)
- [W] <check>: <what is missing or suspicious>
### Info (N)
- [I] <observation>: <what was noted>
---
Summary: N error(s), N warning(s), N info.
If a tier has zero findings, omit that section header entirely. End with one sentence summarising the overall state.
Quick reference
Standard signature forms
| Problem type | Signature |
|---|
| Scalable, unconstrained | function <name>(args...; n::Int = default_nvar, kwargs...) |
| Fixed-size | function <name>(args...; kwargs...) |
| Scalable with extra params | function <name>(args...; n::Int = default_nvar, α::Float64 = 1.0, kwargs...) |
n-adjustment pattern (required form)
function <name>(args...; n::Int = default_nvar, kwargs...)
n_orig = n
n = 4 * max(1, div(n, 4)) # or whatever the adjustment is
@adjust_nvar_warn("<name>", n_orig, n)
...
end
@variable start value patterns (all accepted)
@variable(nlp, x[i = 1:n], start = 1.0) # scalar literal
@variable(nlp, x[i = 1:n], start = x0[i]) # per-element from array
@variable(nlp, 0 <= x[i = 1:n] <= 1, start = 0.5) # bounded with start
set_start_value.(x, x0) # post-hoc batch assignment
Reference examples
- Unconstrained scalable:
src/PureJuMP/arwhead.jl
- Scalable with n-adjustment:
src/PureJuMP/woods.jl, src/PureJuMP/elec.jl
- Scalable multi-parameter family:
src/PureJuMP/dixmaan_efgh.jl
- Constrained scalable:
src/PureJuMP/elec.jl, src/PureJuMP/catmix.jl
- Fixed-size constrained:
src/PureJuMP/hs100.jl
- Data-loading with
_ensure_data!: src/PureJuMP/triangle.jl