Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

trust-constr make origin of error message clearer when there are more equality constraints than independent variables #20665

Merged
merged 3 commits into from
May 8, 2024

Conversation

andyfaff
Copy link
Contributor

@andyfaff andyfaff commented May 8, 2024

Closes #20618. Instead of the user receiving an expected square matrix ValueError when the number of equality constraints is more than the number of independent variables, the error is caught, and reraised with a clearer explanation of a possible cause.

A better way would be to use Exception.add_note, but that method doesn't exist until 3.11. It's also slightly fragile because it relies on the linalg error message not changing.

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File [~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/optimize/_trustregion_constr/equality_constrained_sqp.py:81](http://localhost:8889/lab/workspaces/auto-2/tree/~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/optimize/_trustregion_constr/equality_constrained_sqp.py#line=80), in equality_constrained_sqp(fun_and_constr, grad_and_jac, lagr_hess, x0, fun0, grad0, constr0, jac0, stop_criteria, state, initial_penalty, initial_trust_radius, factorization_method, trust_lb, trust_ub, scaling)
     80 try:
---> 81     Z, LS, Y = projections(A, factorization_method)
     82 except ValueError as e:

File [~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/optimize/_trustregion_constr/projections.py:403](http://localhost:8889/lab/workspaces/auto-2/tree/~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/optimize/_trustregion_constr/projections.py#line=402), in projections(A, method, orth_tol, max_refin, tol)
    400     null_space, least_squares, row_space \
    401         = svd_factorization_projections(A, m, n, orth_tol, max_refin, tol)
--> 403 Z = LinearOperator((n, n), null_space)
    404 LS = LinearOperator((m, n), least_squares)

File [~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/sparse/linalg/_interface.py:584](http://localhost:8889/lab/workspaces/auto-2/tree/~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/sparse/linalg/_interface.py#line=583), in _CustomLinearOperator.__init__(self, shape, matvec, rmatvec, matmat, dtype, rmatmat)
    582 self.__matmat_impl = matmat
--> 584 self._init_dtype()

File [~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/sparse/linalg/_interface.py:182](http://localhost:8889/lab/workspaces/auto-2/tree/~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/sparse/linalg/_interface.py#line=181), in LinearOperator._init_dtype(self)
    181 v = np.zeros(self.shape[-1])
--> 182 self.dtype = np.asarray(self.matvec(v)).dtype

File [~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/sparse/linalg/_interface.py:236](http://localhost:8889/lab/workspaces/auto-2/tree/~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/sparse/linalg/_interface.py#line=235), in LinearOperator.matvec(self, x)
    234     raise ValueError('dimension mismatch')
--> 236 y = self._matvec(x)
    238 if isinstance(x, np.matrix):

File [~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/sparse/linalg/_interface.py:593](http://localhost:8889/lab/workspaces/auto-2/tree/~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/sparse/linalg/_interface.py#line=592), in _CustomLinearOperator._matvec(self, x)
    592 def _matvec(self, x):
--> 593     return self.__matvec_impl(x)

File [~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/optimize/_trustregion_constr/projections.py:194](http://localhost:8889/lab/workspaces/auto-2/tree/~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/optimize/_trustregion_constr/projections.py#line=193), in qr_factorization_projections.<locals>.null_space(x)
    193 aux1 = Q.T.dot(x)
--> 194 aux2 = scipy.linalg.solve_triangular(R, aux1, lower=False)
    195 v = np.zeros(m)

File [~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/linalg/_basic.py:337](http://localhost:8889/lab/workspaces/auto-2/tree/~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/linalg/_basic.py#line=336), in solve_triangular(a, b, trans, lower, unit_diagonal, overwrite_b, check_finite)
    336 if len(a1.shape) != 2 or a1.shape[0] != a1.shape[1]:
--> 337     raise ValueError('expected square matrix')
    339 if a1.shape[0] != b1.shape[0]:

ValueError: expected square matrix

The above exception was the direct cause of the following exception:

ValueError                                Traceback (most recent call last)
Cell In[1], line 19
     13 constraints = [
     14     NonlinearConstraint(constraint1, [0, 0], [0, 0]),
     15 ]
     18 x0 = np.zeros(1)
---> 19 res = minimize(fun, x0, method='trust-constr', constraints=constraints)

File [~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/optimize/_minimize.py:725](http://localhost:8889/lab/workspaces/auto-2/tree/~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/optimize/_minimize.py#line=724), in minimize(fun, x0, args, method, jac, hess, hessp, bounds, constraints, tol, callback, options)
    722     res = _minimize_slsqp(fun, x0, args, jac, bounds,
    723                           constraints, callback=callback, **options)
    724 elif meth == 'trust-constr':
--> 725     res = _minimize_trustregion_constr(fun, x0, args, jac, hess, hessp,
    726                                        bounds, constraints,
    727                                        callback=callback, **options)
    728 elif meth == 'dogleg':
    729     res = _minimize_dogleg(fun, x0, args, jac, hess,
    730                            callback=callback, **options)

File [~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py:519](http://localhost:8889/lab/workspaces/auto-2/tree/~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py#line=518), in _minimize_trustregion_constr(fun, x0, args, grad, hess, hessp, bounds, constraints, xtol, gtol, barrier_tol, sparse_jacobian, callback, maxiter, verbose, finite_diff_rel_step, initial_constr_penalty, initial_tr_radius, initial_barrier_parameter, initial_barrier_tolerance, factorization_method, disp)
    516         J_eq, _ = canonical.jac(x)
    517         return g, J_eq
--> 519     _, result = equality_constrained_sqp(
    520         fun_and_constr, grad_and_jac, lagrangian_hess,
    521         x0, objective.f, objective.g,
    522         c_eq0, J_eq0,
    523         stop_criteria, state,
    524         initial_constr_penalty, initial_tr_radius,
    525         factorization_method)
    527 elif method == 'tr_interior_point':
    528     _, result = tr_interior_point(
    529         objective.fun, objective.grad, lagrangian_hess,
    530         n_vars, canonical.n_ineq, canonical.n_eq,
   (...)
    538         initial_constr_penalty, initial_tr_radius,
    539         factorization_method)

File [~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/optimize/_trustregion_constr/equality_constrained_sqp.py:86](http://localhost:8889/lab/workspaces/auto-2/tree/~/Documents/Andy/programming/scipy/build-install/lib/python3.10/site-packages/scipy/optimize/_trustregion_constr/equality_constrained_sqp.py#line=85), in equality_constrained_sqp(fun_and_constr, grad_and_jac, lagr_hess, x0, fun0, grad0, constr0, jac0, stop_criteria, state, initial_penalty, initial_trust_radius, factorization_method, trust_lb, trust_ub, scaling)
     82 except ValueError as e:
     83     if str(e) == "expected square matrix":
     84         # can be the case if there are more equality
     85         # constraints than independent variables
---> 86         raise ValueError(
     87             "The 'expected square matrix' error can occur if there are"
     88             " more equality constraints than independent variables."
     89             " Consider how your constraints are setup, or use"
     90             " factorization_method='SVDFactorization'."
     91         ) from e
     92     else:
     93         raise e

ValueError: The 'expected square matrix' error can occur if there are more equality constraints than independent variables. Consider how your constraints are setup, or use factorization_method='SVDFactorization'.

…re more equality constraints than independent variables
Copy link
Contributor

@mdhaber mdhaber left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with andyfaff#65

TST: optimize.minimize: test improved error message
@mdhaber
Copy link
Contributor

mdhaber commented May 8, 2024

Will merge when tests pass (if I remember tonight) or tomorrow morning.

@mdhaber mdhaber merged commit e01329a into scipy:main May 8, 2024
30 checks passed
@mdhaber
Copy link
Contributor

mdhaber commented May 8, 2024

Thanks @andyfaff!

@andyfaff andyfaff deleted the gh20618 branch May 8, 2024 05:47
@dschmitz89 dschmitz89 added this to the 1.14.0 milestone May 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants