_constraints.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. """Constraints definition for minimize."""
  2. from __future__ import division, print_function, absolute_import
  3. import numpy as np
  4. from ._hessian_update_strategy import BFGS
  5. from ._differentiable_functions import (
  6. VectorFunction, LinearVectorFunction, IdentityVectorFunction)
  7. from .optimize import OptimizeWarning
  8. from warnings import warn
  9. from scipy.sparse import issparse
  10. class NonlinearConstraint(object):
  11. """Nonlinear constraint on the variables.
  12. The constraint has the general inequality form::
  13. lb <= fun(x) <= ub
  14. Here the vector of independent variables x is passed as ndarray of shape
  15. (n,) and ``fun`` returns a vector with m components.
  16. It is possible to use equal bounds to represent an equality constraint or
  17. infinite bounds to represent a one-sided constraint.
  18. Parameters
  19. ----------
  20. fun : callable
  21. The function defining the constraint.
  22. The signature is ``fun(x) -> array_like, shape (m,)``.
  23. lb, ub : array_like
  24. Lower and upper bounds on the constraint. Each array must have the
  25. shape (m,) or be a scalar, in the latter case a bound will be the same
  26. for all components of the constraint. Use ``np.inf`` with an
  27. appropriate sign to specify a one-sided constraint.
  28. Set components of `lb` and `ub` equal to represent an equality
  29. constraint. Note that you can mix constraints of different types:
  30. interval, one-sided or equality, by setting different components of
  31. `lb` and `ub` as necessary.
  32. jac : {callable, '2-point', '3-point', 'cs'}, optional
  33. Method of computing the Jacobian matrix (an m-by-n matrix,
  34. where element (i, j) is the partial derivative of f[i] with
  35. respect to x[j]). The keywords {'2-point', '3-point',
  36. 'cs'} select a finite difference scheme for the numerical estimation.
  37. A callable must have the following signature:
  38. ``jac(x) -> {ndarray, sparse matrix}, shape (m, n)``.
  39. Default is '2-point'.
  40. hess : {callable, '2-point', '3-point', 'cs', HessianUpdateStrategy, None}, optional
  41. Method for computing the Hessian matrix. The keywords
  42. {'2-point', '3-point', 'cs'} select a finite difference scheme for
  43. numerical estimation. Alternatively, objects implementing
  44. `HessianUpdateStrategy` interface can be used to approximate the
  45. Hessian. Currently available implementations are:
  46. - `BFGS` (default option)
  47. - `SR1`
  48. A callable must return the Hessian matrix of ``dot(fun, v)`` and
  49. must have the following signature:
  50. ``hess(x, v) -> {LinearOperator, sparse matrix, array_like}, shape (n, n)``.
  51. Here ``v`` is ndarray with shape (m,) containing Lagrange multipliers.
  52. keep_feasible : array_like of bool, optional
  53. Whether to keep the constraint components feasible throughout
  54. iterations. A single value set this property for all components.
  55. Default is False. Has no effect for equality constraints.
  56. finite_diff_rel_step: None or array_like, optional
  57. Relative step size for the finite difference approximation. Default is
  58. None, which will select a reasonable value automatically depending
  59. on a finite difference scheme.
  60. finite_diff_jac_sparsity: {None, array_like, sparse matrix}, optional
  61. Defines the sparsity structure of the Jacobian matrix for finite
  62. difference estimation, its shape must be (m, n). If the Jacobian has
  63. only few non-zero elements in *each* row, providing the sparsity
  64. structure will greatly speed up the computations. A zero entry means
  65. that a corresponding element in the Jacobian is identically zero.
  66. If provided, forces the use of 'lsmr' trust-region solver.
  67. If None (default) then dense differencing will be used.
  68. Notes
  69. -----
  70. Finite difference schemes {'2-point', '3-point', 'cs'} may be used for
  71. approximating either the Jacobian or the Hessian. We, however, do not allow
  72. its use for approximating both simultaneously. Hence whenever the Jacobian
  73. is estimated via finite-differences, we require the Hessian to be estimated
  74. using one of the quasi-Newton strategies.
  75. The scheme 'cs' is potentially the most accurate, but requires the function
  76. to correctly handles complex inputs and be analytically continuable to the
  77. complex plane. The scheme '3-point' is more accurate than '2-point' but
  78. requires twice as many operations.
  79. """
  80. def __init__(self, fun, lb, ub, jac='2-point', hess=BFGS(),
  81. keep_feasible=False, finite_diff_rel_step=None,
  82. finite_diff_jac_sparsity=None):
  83. self.fun = fun
  84. self.lb = lb
  85. self.ub = ub
  86. self.finite_diff_rel_step = finite_diff_rel_step
  87. self.finite_diff_jac_sparsity = finite_diff_jac_sparsity
  88. self.jac = jac
  89. self.hess = hess
  90. self.keep_feasible = keep_feasible
  91. class LinearConstraint(object):
  92. """Linear constraint on the variables.
  93. The constraint has the general inequality form::
  94. lb <= A.dot(x) <= ub
  95. Here the vector of independent variables x is passed as ndarray of shape
  96. (n,) and the matrix A has shape (m, n).
  97. It is possible to use equal bounds to represent an equality constraint or
  98. infinite bounds to represent a one-sided constraint.
  99. Parameters
  100. ----------
  101. A : {array_like, sparse matrix}, shape (m, n)
  102. Matrix defining the constraint.
  103. lb, ub : array_like
  104. Lower and upper bounds on the constraint. Each array must have the
  105. shape (m,) or be a scalar, in the latter case a bound will be the same
  106. for all components of the constraint. Use ``np.inf`` with an
  107. appropriate sign to specify a one-sided constraint.
  108. Set components of `lb` and `ub` equal to represent an equality
  109. constraint. Note that you can mix constraints of different types:
  110. interval, one-sided or equality, by setting different components of
  111. `lb` and `ub` as necessary.
  112. keep_feasible : array_like of bool, optional
  113. Whether to keep the constraint components feasible throughout
  114. iterations. A single value set this property for all components.
  115. Default is False. Has no effect for equality constraints.
  116. """
  117. def __init__(self, A, lb, ub, keep_feasible=False):
  118. self.A = A
  119. self.lb = lb
  120. self.ub = ub
  121. self.keep_feasible = keep_feasible
  122. class Bounds(object):
  123. """Bounds constraint on the variables.
  124. The constraint has the general inequality form::
  125. lb <= x <= ub
  126. It is possible to use equal bounds to represent an equality constraint or
  127. infinite bounds to represent a one-sided constraint.
  128. Parameters
  129. ----------
  130. lb, ub : array_like, optional
  131. Lower and upper bounds on independent variables. Each array must
  132. have the same size as x or be a scalar, in which case a bound will be
  133. the same for all the variables. Set components of `lb` and `ub` equal
  134. to fix a variable. Use ``np.inf`` with an appropriate sign to disable
  135. bounds on all or some variables. Note that you can mix constraints of
  136. different types: interval, one-sided or equality, by setting different
  137. components of `lb` and `ub` as necessary.
  138. keep_feasible : array_like of bool, optional
  139. Whether to keep the constraint components feasible throughout
  140. iterations. A single value set this property for all components.
  141. Default is False. Has no effect for equality constraints.
  142. """
  143. def __init__(self, lb, ub, keep_feasible=False):
  144. self.lb = lb
  145. self.ub = ub
  146. self.keep_feasible = keep_feasible
  147. class PreparedConstraint(object):
  148. """Constraint prepared from a user defined constraint.
  149. On creation it will check whether a constraint definition is valid and
  150. the initial point is feasible. If created successfully, it will contain
  151. the attributes listed below.
  152. Parameters
  153. ----------
  154. constraint : {NonlinearConstraint, LinearConstraint`, Bounds}
  155. Constraint to check and prepare.
  156. x0 : array_like
  157. Initial vector of independent variables.
  158. sparse_jacobian : bool or None, optional
  159. If bool, then the Jacobian of the constraint will be converted
  160. to the corresponded format if necessary. If None (default), such
  161. conversion is not made.
  162. finite_diff_bounds : 2-tuple, optional
  163. Lower and upper bounds on the independent variables for the finite
  164. difference approximation, if applicable. Defaults to no bounds.
  165. Attributes
  166. ----------
  167. fun : {VectorFunction, LinearVectorFunction, IdentityVectorFunction}
  168. Function defining the constraint wrapped by one of the convenience
  169. classes.
  170. bounds : 2-tuple
  171. Contains lower and upper bounds for the constraints --- lb and ub.
  172. These are converted to ndarray and have a size equal to the number of
  173. the constraints.
  174. keep_feasible : ndarray
  175. Array indicating which components must be kept feasible with a size
  176. equal to the number of the constraints.
  177. """
  178. def __init__(self, constraint, x0, sparse_jacobian=None,
  179. finite_diff_bounds=(-np.inf, np.inf)):
  180. if isinstance(constraint, NonlinearConstraint):
  181. fun = VectorFunction(constraint.fun, x0,
  182. constraint.jac, constraint.hess,
  183. constraint.finite_diff_rel_step,
  184. constraint.finite_diff_jac_sparsity,
  185. finite_diff_bounds, sparse_jacobian)
  186. elif isinstance(constraint, LinearConstraint):
  187. fun = LinearVectorFunction(constraint.A, x0, sparse_jacobian)
  188. elif isinstance(constraint, Bounds):
  189. fun = IdentityVectorFunction(x0, sparse_jacobian)
  190. else:
  191. raise ValueError("`constraint` of an unknown type is passed.")
  192. m = fun.m
  193. lb = np.asarray(constraint.lb, dtype=float)
  194. ub = np.asarray(constraint.ub, dtype=float)
  195. if lb.ndim == 0:
  196. lb = np.resize(lb, m)
  197. if ub.ndim == 0:
  198. ub = np.resize(ub, m)
  199. keep_feasible = np.asarray(constraint.keep_feasible, dtype=bool)
  200. if keep_feasible.ndim == 0:
  201. keep_feasible = np.resize(keep_feasible, m)
  202. if keep_feasible.shape != (m,):
  203. raise ValueError("`keep_feasible` has a wrong shape.")
  204. mask = keep_feasible & (lb != ub)
  205. f0 = fun.f
  206. if np.any(f0[mask] < lb[mask]) or np.any(f0[mask] > ub[mask]):
  207. raise ValueError("`x0` is infeasible with respect to some "
  208. "inequality constraint with `keep_feasible` "
  209. "set to True.")
  210. self.fun = fun
  211. self.bounds = (lb, ub)
  212. self.keep_feasible = keep_feasible
  213. def new_bounds_to_old(lb, ub, n):
  214. """Convert the new bounds representation to the old one.
  215. The new representation is a tuple (lb, ub) and the old one is a list
  216. containing n tuples, i-th containing lower and upper bound on a i-th
  217. variable.
  218. """
  219. lb = np.asarray(lb)
  220. ub = np.asarray(ub)
  221. if lb.ndim == 0:
  222. lb = np.resize(lb, n)
  223. if ub.ndim == 0:
  224. ub = np.resize(ub, n)
  225. lb = [x if x > -np.inf else None for x in lb]
  226. ub = [x if x < np.inf else None for x in ub]
  227. return list(zip(lb, ub))
  228. def old_bound_to_new(bounds):
  229. """Convert the old bounds representation to the new one.
  230. The new representation is a tuple (lb, ub) and the old one is a list
  231. containing n tuples, i-th containing lower and upper bound on a i-th
  232. variable.
  233. """
  234. lb, ub = zip(*bounds)
  235. lb = np.array([x if x is not None else -np.inf for x in lb])
  236. ub = np.array([x if x is not None else np.inf for x in ub])
  237. return lb, ub
  238. def strict_bounds(lb, ub, keep_feasible, n_vars):
  239. """Remove bounds which are not asked to be kept feasible."""
  240. strict_lb = np.resize(lb, n_vars).astype(float)
  241. strict_ub = np.resize(ub, n_vars).astype(float)
  242. keep_feasible = np.resize(keep_feasible, n_vars)
  243. strict_lb[~keep_feasible] = -np.inf
  244. strict_ub[~keep_feasible] = np.inf
  245. return strict_lb, strict_ub
  246. def new_constraint_to_old(con, x0):
  247. """
  248. Converts new-style constraint objects to old-style constraint dictionaries.
  249. """
  250. if isinstance(con, NonlinearConstraint):
  251. if (con.finite_diff_jac_sparsity is not None or
  252. con.finite_diff_rel_step is not None or
  253. not isinstance(con.hess, BFGS) or # misses user specified BFGS
  254. con.keep_feasible):
  255. warn("Constraint options `finite_diff_jac_sparsity`, "
  256. "`finite_diff_rel_step`, `keep_feasible`, and `hess`"
  257. "are ignored by this method.", OptimizeWarning)
  258. fun = con.fun
  259. if callable(con.jac):
  260. jac = con.jac
  261. else:
  262. jac = None
  263. else: # LinearConstraint
  264. if con.keep_feasible:
  265. warn("Constraint option `keep_feasible` is ignored by this "
  266. "method.", OptimizeWarning)
  267. A = con.A
  268. if issparse(A):
  269. A = A.todense()
  270. fun = lambda x: np.dot(A, x)
  271. jac = lambda x: A
  272. # FIXME: when bugs in VectorFunction/LinearVectorFunction are worked out,
  273. # use pcon.fun.fun and pcon.fun.jac. Until then, get fun/jac above.
  274. pcon = PreparedConstraint(con, x0)
  275. lb, ub = pcon.bounds
  276. i_eq = lb == ub
  277. i_bound_below = np.logical_xor(lb != -np.inf, i_eq)
  278. i_bound_above = np.logical_xor(ub != np.inf, i_eq)
  279. i_unbounded = np.logical_and(lb == -np.inf, ub == np.inf)
  280. if np.any(i_unbounded):
  281. warn("At least one constraint is unbounded above and below. Such "
  282. "constraints are ignored.", OptimizeWarning)
  283. ceq = []
  284. if np.any(i_eq):
  285. def f_eq(x):
  286. y = np.array(fun(x)).flatten()
  287. return y[i_eq] - lb[i_eq]
  288. ceq = [{"type": "eq", "fun": f_eq}]
  289. if jac is not None:
  290. def j_eq(x):
  291. dy = jac(x)
  292. if issparse(dy):
  293. dy = dy.todense()
  294. dy = np.atleast_2d(dy)
  295. return dy[i_eq, :]
  296. ceq[0]["jac"] = j_eq
  297. cineq = []
  298. n_bound_below = np.sum(i_bound_below)
  299. n_bound_above = np.sum(i_bound_above)
  300. if n_bound_below + n_bound_above:
  301. def f_ineq(x):
  302. y = np.zeros(n_bound_below + n_bound_above)
  303. y_all = np.array(fun(x)).flatten()
  304. y[:n_bound_below] = y_all[i_bound_below] - lb[i_bound_below]
  305. y[n_bound_below:] = -(y_all[i_bound_above] - ub[i_bound_above])
  306. return y
  307. cineq = [{"type": "ineq", "fun": f_ineq}]
  308. if jac is not None:
  309. def j_ineq(x):
  310. dy = np.zeros((n_bound_below + n_bound_above, len(x0)))
  311. dy_all = jac(x)
  312. if issparse(dy_all):
  313. dy_all = dy_all.todense()
  314. dy_all = np.atleast_2d(dy_all)
  315. dy[:n_bound_below, :] = dy_all[i_bound_below]
  316. dy[n_bound_below:, :] = -dy_all[i_bound_above]
  317. return dy
  318. cineq[0]["jac"] = j_ineq
  319. old_constraints = ceq + cineq
  320. if len(old_constraints) > 1:
  321. warn("Equality and inequality constraints are specified in the same "
  322. "element of the constraint list. For efficient use with this "
  323. "method, equality and inequality constraints should be specified "
  324. "in separate elements of the constraint list. ", OptimizeWarning)
  325. return old_constraints
  326. def old_constraint_to_new(ic, con):
  327. """
  328. Converts old-style constraint dictionaries to new-style constraint objects.
  329. """
  330. # check type
  331. try:
  332. ctype = con['type'].lower()
  333. except KeyError:
  334. raise KeyError('Constraint %d has no type defined.' % ic)
  335. except TypeError:
  336. raise TypeError('Constraints must be a sequence of dictionaries.')
  337. except AttributeError:
  338. raise TypeError("Constraint's type must be a string.")
  339. else:
  340. if ctype not in ['eq', 'ineq']:
  341. raise ValueError("Unknown constraint type '%s'." % con['type'])
  342. if 'fun' not in con:
  343. raise ValueError('Constraint %d has no function defined.' % ic)
  344. lb = 0
  345. if ctype == 'eq':
  346. ub = 0
  347. else:
  348. ub = np.inf
  349. jac = '2-point'
  350. if 'args' in con:
  351. args = con['args']
  352. fun = lambda x: con['fun'](x, *args)
  353. if 'jac' in con:
  354. jac = lambda x: con['jac'](x, *args)
  355. else:
  356. fun = con['fun']
  357. if 'jac' in con:
  358. jac = con['jac']
  359. return NonlinearConstraint(fun, lb, ub, jac)