Simplify the implementation of jprod!#564
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors jprod!() in the NLP API to reduce branching by using short-circuit calls for the linear and nonlinear Jacobian-vector products.
Changes:
- Replaced nested
if/elselogic injprod!with&&short-circuit calls. - Always passes sliced destinations (
view(Jv, ...)) tojprod_lin!andjprod_nln!.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| get_nlin(nlp.meta) > 0 && jprod_lin!(nlp, x, v, view(Jv, get_lin(nlp.meta))) | ||
| get_nnln(nlp.meta) > 0 && jprod_nln!(nlp, x, v, view(Jv, get_nln(nlp.meta))) |
There was a problem hiding this comment.
jprod! now always calls jprod_lin!/jprod_nln! with view(Jv, ...). This removes the previous fast paths for the all-linear (get_nnln(meta)==0) and all-nonlinear (get_nlin(meta)==0) cases, and changes the type of the destination passed to downstream implementations from a plain Vector to a SubArray even when no remapping is needed. That can be a breaking change for model implementations that specialized jprod_lin!/jprod_nln! on concrete vectors (and it introduces extra indirection for the common contiguous/full-range case). Consider restoring the old branching (as done in cons! / jac_coord!) or conditionally passing Jv directly when the constraint set is purely linear or purely nonlinear.
| get_nlin(nlp.meta) > 0 && jprod_lin!(nlp, x, v, view(Jv, get_lin(nlp.meta))) | |
| get_nnln(nlp.meta) > 0 && jprod_nln!(nlp, x, v, view(Jv, get_nln(nlp.meta))) | |
| if get_nnln(nlp.meta) == 0 | |
| jprod_lin!(nlp, x, v, Jv) | |
| elseif get_nlin(nlp.meta) == 0 | |
| jprod_nln!(nlp, x, v, Jv) | |
| else | |
| jprod_lin!(nlp, x, v, view(Jv, get_lin(nlp.meta))) | |
| jprod_nln!(nlp, x, v, view(Jv, get_nln(nlp.meta))) | |
| end |
| get_nlin(nlp.meta) > 0 && jprod_lin!(nlp, x, v, view(Jv, get_lin(nlp.meta))) | ||
| get_nnln(nlp.meta) > 0 && jprod_nln!(nlp, x, v, view(Jv, get_nln(nlp.meta))) |
There was a problem hiding this comment.
There are no tests covering the edge cases where all constraints are linear (nnln==0) or all constraints are nonlinear (nlin==0). Since this change alters how jprod! dispatches (passing views vs the full vector), adding targeted tests for those two scenarios would help prevent regressions and catch downstream dispatch/type issues.
| get_nlin(nlp.meta) > 0 && jprod_lin!(nlp, x, v, view(Jv, get_lin(nlp.meta))) | |
| get_nnln(nlp.meta) > 0 && jprod_nln!(nlp, x, v, view(Jv, get_nln(nlp.meta))) | |
| ncon = get_ncon(nlp.meta) | |
| nlin = get_nlin(nlp.meta) | |
| nnln = get_nnln(nlp.meta) | |
| if nlin > 0 | |
| if nlin == ncon | |
| jprod_lin!(nlp, x, v, Jv) | |
| else | |
| jprod_lin!(nlp, x, v, view(Jv, get_lin(nlp.meta))) | |
| end | |
| end | |
| if nnln > 0 | |
| if nnln == ncon | |
| jprod_nln!(nlp, x, v, Jv) | |
| else | |
| jprod_nln!(nlp, x, v, view(Jv, get_nln(nlp.meta))) | |
| end | |
| end |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| get_nlin(nlp.meta) > 0 && cons_lin!(nlp, x, view(cx, get_lin(nlp.meta))) | ||
| get_nnln(nlp.meta) > 0 && cons_nln!(nlp, x, view(cx, get_nln(nlp.meta))) |
There was a problem hiding this comment.
cons! now always forwards cx to cons_lin!/cons_nln! as view(cx, meta.lin/meta.nln). When the problem is purely linear or purely nonlinear, this changes the argument type from the original cx (often a Vector/StridedVector) to a SubArray with index-vector indirection, which can break downstream model implementations that dispatch on Vector/StridedVector or use BLAS/external kernels requiring strided storage, and can add avoidable overhead. Consider restoring the fast-path branches for get_nnln(meta)==0 / get_nlin(meta)==0 (pass cx directly) and only using indexed views when both linear and nonlinear constraints are present.
| get_nlin(nlp.meta) > 0 && cons_lin!(nlp, x, view(cx, get_lin(nlp.meta))) | |
| get_nnln(nlp.meta) > 0 && cons_nln!(nlp, x, view(cx, get_nln(nlp.meta))) | |
| nlin = get_nlin(nlp.meta) | |
| nnln = get_nnln(nlp.meta) | |
| if nnln == 0 | |
| cons_lin!(nlp, x, cx) | |
| elseif nlin == 0 | |
| cons_nln!(nlp, x, cx) | |
| else | |
| cons_lin!(nlp, x, view(cx, get_lin(nlp.meta))) | |
| cons_nln!(nlp, x, view(cx, get_nln(nlp.meta))) | |
| end |
| if get_nlin(nlp.meta) > 0 | ||
| if get_nnln(nlp.meta) == 0 | ||
| jac_lin_coord!(nlp, x, vals) | ||
| else | ||
| lin_ind = 1:(get_lin_nnzj(nlp.meta)) | ||
| jac_lin_coord!(nlp, x, view(vals, lin_ind)) | ||
| end | ||
| lin_ind = 1:(get_lin_nnzj(nlp.meta)) | ||
| jac_lin_coord!(nlp, x, view(vals, lin_ind)) | ||
| end | ||
| if get_nnln(nlp.meta) > 0 | ||
| if get_nlin(nlp.meta) == 0 | ||
| jac_nln_coord!(nlp, x, vals) | ||
| else | ||
| nln_ind = (get_lin_nnzj(nlp.meta) + 1):(get_lin_nnzj(nlp.meta) + get_nln_nnzj(nlp.meta)) | ||
| jac_nln_coord!(nlp, x, view(vals, nln_ind)) | ||
| end | ||
| nln_ind = (get_lin_nnzj(nlp.meta) + 1):(get_lin_nnzj(nlp.meta) + get_nln_nnzj(nlp.meta)) | ||
| jac_nln_coord!(nlp, x, view(vals, nln_ind)) |
There was a problem hiding this comment.
jac_coord! now always passes view(vals, lin_ind/nln_ind) into jac_lin_coord!/jac_nln_coord! even when the Jacobian is entirely linear or entirely nonlinear. This changes the concrete type seen by model implementations from the original vals (commonly a Vector) to a SubArray, which may break downstream methods that dispatch on Vector/StridedVector or call routines requiring strided storage. Suggest keeping the previous single-block fast paths (call jac_*_coord! with vals directly when the other block is empty) and using views only for the mixed case.
| get_nlin(nlp.meta) > 0 && jprod_lin!(nlp, x, v, view(Jv, get_lin(nlp.meta))) | ||
| get_nnln(nlp.meta) > 0 && jprod_nln!(nlp, x, v, view(Jv, get_nln(nlp.meta))) |
There was a problem hiding this comment.
jprod! now always calls jprod_lin!/jprod_nln! with view(Jv, meta.lin/meta.nln). For purely linear or purely nonlinear problems this changes the Jv argument from the original array (often a Vector/StridedVector) to an indexed SubArray, which can break downstream implementations that dispatch on Vector/StridedVector or use BLAS/external kernels, and may add overhead compared to the previous direct call. Consider reinstating the get_nnln(meta)==0 / get_nlin(meta)==0 fast paths (pass Jv directly) and only using indexed views when both constraint types are present.
| get_nlin(nlp.meta) > 0 && jprod_lin!(nlp, x, v, view(Jv, get_lin(nlp.meta))) | |
| get_nnln(nlp.meta) > 0 && jprod_nln!(nlp, x, v, view(Jv, get_nln(nlp.meta))) | |
| if get_nnln(nlp.meta) == 0 | |
| jprod_lin!(nlp, x, v, Jv) | |
| elseif get_nlin(nlp.meta) == 0 | |
| jprod_nln!(nlp, x, v, Jv) | |
| else | |
| jprod_lin!(nlp, x, v, view(Jv, get_lin(nlp.meta))) | |
| jprod_nln!(nlp, x, v, view(Jv, get_nln(nlp.meta))) | |
| end |
|
Oh I see, thank you. It's really too bad that views cause so much trouble. I'll close this then. |
|
Views could be fine as long as they are strided. If they are not strided, we can't use BLAS / LAPACK (CPU and GPU) and Julia dispatches to generic fallbacks. |
I find
jprod!()unnecessarily complicated.