test_optimize.py 140 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603
  1. """
  2. Unit tests for optimization routines from optimize.py
  3. Authors:
  4. Ed Schofield, Nov 2005
  5. Andrew Straw, April 2008
  6. """
  7. import sys
  8. import itertools
  9. import inspect
  10. import platform
  11. import threading
  12. import warnings
  13. import multiprocessing
  14. import numpy as np
  15. from numpy.testing import (assert_allclose, assert_equal,
  16. assert_almost_equal,
  17. assert_no_warnings,
  18. assert_array_less)
  19. import pytest
  20. from pytest import raises as assert_raises
  21. import scipy
  22. from scipy._lib._gcutils import assert_deallocated
  23. from scipy import optimize
  24. from scipy.optimize._minimize import Bounds, NonlinearConstraint
  25. from scipy.optimize._minimize import (MINIMIZE_METHODS,
  26. MINIMIZE_METHODS_NEW_CB,
  27. MINIMIZE_SCALAR_METHODS)
  28. from scipy.optimize._linprog import LINPROG_METHODS
  29. from scipy.optimize._root import ROOT_METHODS
  30. from scipy.optimize._root_scalar import ROOT_SCALAR_METHODS
  31. from scipy.optimize._qap import QUADRATIC_ASSIGNMENT_METHODS
  32. from scipy.optimize._differentiable_functions import ScalarFunction, FD_METHODS
  33. from scipy.optimize._optimize import (
  34. MemoizeJac, show_options, OptimizeResult, _minimize_bfgs
  35. )
  36. from scipy.optimize import rosen, rosen_der, rosen_hess
  37. from scipy.sparse import (coo_matrix, csc_matrix, csr_matrix, coo_array,
  38. csr_array, csc_array)
  39. from scipy._lib._array_api_no_0d import xp_assert_equal
  40. from scipy._lib._array_api import make_xp_test_case
  41. from scipy._lib._util import MapWrapper
  42. lazy_xp_modules = [optimize]
  43. def test_check_grad():
  44. # Verify if check_grad is able to estimate the derivative of the
  45. # expit (logistic sigmoid) function.
  46. def expit(x):
  47. return 1 / (1 + np.exp(-x))
  48. def der_expit(x):
  49. return np.exp(-x) / (1 + np.exp(-x))**2
  50. x0 = np.array([1.5])
  51. r = optimize.check_grad(expit, der_expit, x0)
  52. assert_almost_equal(r, 0)
  53. # SPEC-007 leave one call with seed to check it still works
  54. r = optimize.check_grad(expit, der_expit, x0,
  55. direction='random', seed=1234)
  56. assert_almost_equal(r, 0)
  57. r = optimize.check_grad(expit, der_expit, x0, epsilon=1e-6)
  58. assert_almost_equal(r, 0)
  59. r = optimize.check_grad(expit, der_expit, x0, epsilon=1e-6,
  60. direction='random', rng=1234)
  61. assert_almost_equal(r, 0)
  62. # Check if the epsilon parameter is being considered.
  63. r = abs(optimize.check_grad(expit, der_expit, x0, epsilon=1e-1) - 0)
  64. assert r > 1e-7
  65. r = abs(optimize.check_grad(expit, der_expit, x0, epsilon=1e-1,
  66. direction='random', rng=1234) - 0)
  67. assert r > 1e-7
  68. def x_sinx(x):
  69. return (x*np.sin(x)).sum()
  70. def der_x_sinx(x):
  71. return np.sin(x) + x*np.cos(x)
  72. x0 = np.arange(0, 2, 0.2)
  73. r = optimize.check_grad(x_sinx, der_x_sinx, x0,
  74. direction='random', rng=1234)
  75. assert_almost_equal(r, 0)
  76. assert_raises(ValueError, optimize.check_grad,
  77. x_sinx, der_x_sinx, x0,
  78. direction='random_projection', rng=1234)
  79. # checking can be done for derivatives of vector valued functions
  80. r = optimize.check_grad(himmelblau_grad, himmelblau_hess, himmelblau_x0,
  81. direction='all', rng=1234)
  82. assert r < 5e-7
  83. class CheckOptimize:
  84. """ Base test case for a simple constrained entropy maximization problem
  85. (the machine translation example of Berger et al in
  86. Computational Linguistics, vol 22, num 1, pp 39--72, 1996.)
  87. """
  88. def setup_method(self):
  89. self.F = np.array([[1, 1, 1],
  90. [1, 1, 0],
  91. [1, 0, 1],
  92. [1, 0, 0],
  93. [1, 0, 0]])
  94. self.K = np.array([1., 0.3, 0.5])
  95. self.startparams = np.zeros(3, np.float64)
  96. self.solution = np.array([0., -0.524869316, 0.487525860])
  97. self.maxiter = 1000
  98. self.funccalls = threading.local()
  99. self.gradcalls = threading.local()
  100. self.trace = threading.local()
  101. def func(self, x):
  102. if not hasattr(self.funccalls, 'c'):
  103. self.funccalls.c = 0
  104. if not hasattr(self.gradcalls, 'c'):
  105. self.gradcalls.c = 0
  106. self.funccalls.c += 1
  107. if self.funccalls.c > 6000:
  108. raise RuntimeError("too many iterations in optimization routine")
  109. log_pdot = np.dot(self.F, x)
  110. logZ = np.log(sum(np.exp(log_pdot)))
  111. f = logZ - np.dot(self.K, x)
  112. if not hasattr(self.trace, 't'):
  113. self.trace.t = []
  114. self.trace.t.append(np.copy(x))
  115. return f
  116. def grad(self, x):
  117. if not hasattr(self.gradcalls, 'c'):
  118. self.gradcalls.c = 0
  119. self.gradcalls.c += 1
  120. log_pdot = np.dot(self.F, x)
  121. logZ = np.log(sum(np.exp(log_pdot)))
  122. p = np.exp(log_pdot - logZ)
  123. return np.dot(self.F.transpose(), p) - self.K
  124. def hess(self, x):
  125. log_pdot = np.dot(self.F, x)
  126. logZ = np.log(sum(np.exp(log_pdot)))
  127. p = np.exp(log_pdot - logZ)
  128. return np.dot(self.F.T,
  129. np.dot(np.diag(p), self.F - np.dot(self.F.T, p)))
  130. def hessp(self, x, p):
  131. return np.dot(self.hess(x), p)
  132. class CheckOptimizeParameterized(CheckOptimize):
  133. def test_cg(self):
  134. # conjugate gradient optimization routine
  135. if self.use_wrapper:
  136. opts = {'maxiter': self.maxiter, 'disp': self.disp,
  137. 'return_all': False}
  138. res = optimize.minimize(self.func, self.startparams, args=(),
  139. method='CG', jac=self.grad,
  140. options=opts)
  141. params, fopt, func_calls, grad_calls, warnflag = \
  142. res['x'], res['fun'], res['nfev'], res['njev'], res['status']
  143. else:
  144. retval = optimize.fmin_cg(self.func, self.startparams,
  145. self.grad, (), maxiter=self.maxiter,
  146. full_output=True, disp=self.disp,
  147. retall=False)
  148. (params, fopt, func_calls, grad_calls, warnflag) = retval
  149. assert_allclose(self.func(params), self.func(self.solution),
  150. atol=1e-6)
  151. # Ensure that function call counts are 'known good'; these are from
  152. # SciPy 0.7.0. Don't allow them to increase.
  153. assert self.funccalls.c == 9, self.funccalls.c
  154. assert self.gradcalls.c == 7, self.gradcalls.c
  155. # Ensure that the function behaves the same; this is from SciPy 0.7.0
  156. assert_allclose(self.trace.t[2:4],
  157. [[0, -0.5, 0.5],
  158. [0, -5.05700028e-01, 4.95985862e-01]],
  159. atol=1e-14, rtol=1e-7)
  160. def test_cg_cornercase(self):
  161. def f(r):
  162. return 2.5 * (1 - np.exp(-1.5*(r - 0.5)))**2
  163. # Check several initial guesses. (Too far away from the
  164. # minimum, the function ends up in the flat region of exp.)
  165. for x0 in np.linspace(-0.75, 3, 71):
  166. sol = optimize.minimize(f, [x0], method='CG')
  167. assert sol.success
  168. assert_allclose(sol.x, [0.5], rtol=1e-5)
  169. def test_bfgs(self):
  170. # Broyden-Fletcher-Goldfarb-Shanno optimization routine
  171. if self.use_wrapper:
  172. opts = {'maxiter': self.maxiter, 'disp': self.disp,
  173. 'return_all': False}
  174. res = optimize.minimize(self.func, self.startparams,
  175. jac=self.grad, method='BFGS', args=(),
  176. options=opts)
  177. params, fopt, gopt, Hopt, func_calls, grad_calls, warnflag = (
  178. res['x'], res['fun'], res['jac'], res['hess_inv'],
  179. res['nfev'], res['njev'], res['status'])
  180. else:
  181. retval = optimize.fmin_bfgs(self.func, self.startparams, self.grad,
  182. args=(), maxiter=self.maxiter,
  183. full_output=True, disp=self.disp,
  184. retall=False)
  185. (params, fopt, gopt, Hopt,
  186. func_calls, grad_calls, warnflag) = retval
  187. assert_allclose(self.func(params), self.func(self.solution),
  188. atol=1e-6)
  189. # Ensure that function call counts are 'known good'; these are from
  190. # SciPy 0.7.0. Don't allow them to increase.
  191. assert self.funccalls.c == 10, self.funccalls.c
  192. assert self.gradcalls.c == 8, self.gradcalls.c
  193. # Ensure that the function behaves the same; this is from SciPy 0.7.0
  194. assert_allclose(self.trace.t[6:8],
  195. [[0, -5.25060743e-01, 4.87748473e-01],
  196. [0, -5.24885582e-01, 4.87530347e-01]],
  197. atol=1e-14, rtol=1e-7)
  198. def test_bfgs_hess_inv0_neg(self):
  199. # Ensure that BFGS does not accept neg. def. initial inverse
  200. # Hessian estimate.
  201. with pytest.raises(ValueError, match="'hess_inv0' matrix isn't "
  202. "positive definite."):
  203. x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])
  204. opts = {'disp': self.disp, 'hess_inv0': -np.eye(5)}
  205. optimize.minimize(optimize.rosen, x0=x0, method='BFGS', args=(),
  206. options=opts)
  207. def test_bfgs_hess_inv0_semipos(self):
  208. # Ensure that BFGS does not accept semi pos. def. initial inverse
  209. # Hessian estimate.
  210. with pytest.raises(ValueError, match="'hess_inv0' matrix isn't "
  211. "positive definite."):
  212. x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])
  213. hess_inv0 = np.eye(5)
  214. hess_inv0[0, 0] = 0
  215. opts = {'disp': self.disp, 'hess_inv0': hess_inv0}
  216. optimize.minimize(optimize.rosen, x0=x0, method='BFGS', args=(),
  217. options=opts)
  218. def test_bfgs_hess_inv0_sanity(self):
  219. # Ensure that BFGS handles `hess_inv0` parameter correctly.
  220. fun = optimize.rosen
  221. x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])
  222. opts = {'disp': self.disp, 'hess_inv0': 1e-2 * np.eye(5)}
  223. res = optimize.minimize(fun, x0=x0, method='BFGS', args=(),
  224. options=opts)
  225. res_true = optimize.minimize(fun, x0=x0, method='BFGS', args=(),
  226. options={'disp': self.disp})
  227. assert_allclose(res.fun, res_true.fun, atol=1e-6)
  228. @pytest.mark.filterwarnings('ignore::UserWarning')
  229. def test_bfgs_infinite(self):
  230. # Test corner case where -Inf is the minimum. See gh-2019.
  231. def func(x):
  232. return -np.e ** (-x)
  233. def fprime(x):
  234. return -func(x)
  235. x0 = [0]
  236. with np.errstate(over='ignore'):
  237. if self.use_wrapper:
  238. opts = {'disp': self.disp}
  239. x = optimize.minimize(func, x0, jac=fprime, method='BFGS',
  240. args=(), options=opts)['x']
  241. else:
  242. x = optimize.fmin_bfgs(func, x0, fprime, disp=self.disp)
  243. assert not np.isfinite(func(x))
  244. def test_bfgs_xrtol(self):
  245. # test for #17345 to test xrtol parameter
  246. x0 = [1.3, 0.7, 0.8, 1.9, 1.2]
  247. res = optimize.minimize(optimize.rosen,
  248. x0, method='bfgs', options={'xrtol': 1e-3})
  249. ref = optimize.minimize(optimize.rosen,
  250. x0, method='bfgs', options={'gtol': 1e-3})
  251. assert res.nit != ref.nit
  252. def test_bfgs_c1(self):
  253. # test for #18977 insufficiently low value of c1 leads to precision loss
  254. # for poor starting parameters
  255. x0 = [10.3, 20.7, 10.8, 1.9, -1.2]
  256. res_c1_small = optimize.minimize(optimize.rosen,
  257. x0, method='bfgs', options={'c1': 1e-8})
  258. res_c1_big = optimize.minimize(optimize.rosen,
  259. x0, method='bfgs', options={'c1': 1e-1})
  260. assert res_c1_small.nfev > res_c1_big.nfev
  261. def test_bfgs_c2(self):
  262. # test that modification of c2 parameter
  263. # results in different number of iterations
  264. x0 = [1.3, 0.7, 0.8, 1.9, 1.2]
  265. res_default = optimize.minimize(optimize.rosen,
  266. x0, method='bfgs', options={'c2': .9})
  267. res_mod = optimize.minimize(optimize.rosen,
  268. x0, method='bfgs', options={'c2': 1e-2})
  269. assert res_default.nit > res_mod.nit
  270. @pytest.mark.parametrize(["c1", "c2"], [[0.5, 2],
  271. [-0.1, 0.1],
  272. [0.2, 0.1]])
  273. def test_invalid_c1_c2(self, c1, c2):
  274. with pytest.raises(ValueError, match="'c1' and 'c2'"):
  275. x0 = [10.3, 20.7, 10.8, 1.9, -1.2]
  276. optimize.minimize(optimize.rosen, x0, method='cg',
  277. options={'c1': c1, 'c2': c2})
  278. def test_powell(self):
  279. # Powell (direction set) optimization routine
  280. if self.use_wrapper:
  281. opts = {'maxiter': self.maxiter, 'disp': self.disp,
  282. 'return_all': False}
  283. res = optimize.minimize(self.func, self.startparams, args=(),
  284. method='Powell', options=opts)
  285. params, fopt, direc, numiter, func_calls, warnflag = (
  286. res['x'], res['fun'], res['direc'], res['nit'],
  287. res['nfev'], res['status'])
  288. else:
  289. retval = optimize.fmin_powell(self.func, self.startparams,
  290. args=(), maxiter=self.maxiter,
  291. full_output=True, disp=self.disp,
  292. retall=False)
  293. (params, fopt, direc, numiter, func_calls, warnflag) = retval
  294. assert_allclose(self.func(params), self.func(self.solution),
  295. atol=1e-6)
  296. # params[0] does not affect the objective function
  297. assert_allclose(params[1:], self.solution[1:], atol=5e-6)
  298. # Ensure that function call counts are 'known good'; these are from
  299. # SciPy 0.7.0. Don't allow them to increase.
  300. #
  301. # However, some leeway must be added: the exact evaluation
  302. # count is sensitive to numerical error, and floating-point
  303. # computations are not bit-for-bit reproducible across
  304. # machines, and when using e.g., MKL, data alignment
  305. # etc., affect the rounding error.
  306. #
  307. assert self.funccalls.c <= 116 + 20, self.funccalls.c
  308. assert self.gradcalls.c == 0, self.gradcalls.c
  309. @pytest.mark.xfail(reason="This part of test_powell fails on some "
  310. "platforms, but the solution returned by powell is "
  311. "still valid.")
  312. def test_powell_gh14014(self):
  313. # This part of test_powell started failing on some CI platforms;
  314. # see gh-14014. Since the solution is still correct and the comments
  315. # in test_powell suggest that small differences in the bits are known
  316. # to change the "trace" of the solution, seems safe to xfail to get CI
  317. # green now and investigate later.
  318. # Powell (direction set) optimization routine
  319. if self.use_wrapper:
  320. opts = {'maxiter': self.maxiter, 'disp': self.disp,
  321. 'return_all': False}
  322. res = optimize.minimize(self.func, self.startparams, args=(),
  323. method='Powell', options=opts)
  324. params, fopt, direc, numiter, func_calls, warnflag = (
  325. res['x'], res['fun'], res['direc'], res['nit'],
  326. res['nfev'], res['status'])
  327. else:
  328. retval = optimize.fmin_powell(self.func, self.startparams,
  329. args=(), maxiter=self.maxiter,
  330. full_output=True, disp=self.disp,
  331. retall=False)
  332. (params, fopt, direc, numiter, func_calls, warnflag) = retval
  333. # Ensure that the function behaves the same; this is from SciPy 0.7.0
  334. assert_allclose(self.trace[34:39],
  335. [[0.72949016, -0.44156936, 0.47100962],
  336. [0.72949016, -0.44156936, 0.48052496],
  337. [1.45898031, -0.88313872, 0.95153458],
  338. [0.72949016, -0.44156936, 0.47576729],
  339. [1.72949016, -0.44156936, 0.47576729]],
  340. atol=1e-14, rtol=1e-7)
  341. def test_powell_bounded(self):
  342. # Powell (direction set) optimization routine
  343. # same as test_powell above, but with bounds
  344. bounds = [(-np.pi, np.pi) for _ in self.startparams]
  345. if self.use_wrapper:
  346. opts = {'maxiter': self.maxiter, 'disp': self.disp,
  347. 'return_all': False}
  348. res = optimize.minimize(self.func, self.startparams, args=(),
  349. bounds=bounds,
  350. method='Powell', options=opts)
  351. params, func_calls = (res['x'], res['nfev'])
  352. assert func_calls == self.funccalls.c
  353. assert_allclose(self.func(params), self.func(self.solution),
  354. atol=1e-6, rtol=1e-5)
  355. # The exact evaluation count is sensitive to numerical error, and
  356. # floating-point computations are not bit-for-bit reproducible
  357. # across machines, and when using e.g. MKL, data alignment etc.
  358. # affect the rounding error.
  359. # It takes 155 calls on my machine, but we can add the same +20
  360. # margin as is used in `test_powell`
  361. assert self.funccalls.c <= 155 + 20
  362. assert self.gradcalls.c == 0
  363. def test_neldermead(self):
  364. # Nelder-Mead simplex algorithm
  365. if self.use_wrapper:
  366. opts = {'maxiter': self.maxiter, 'disp': self.disp,
  367. 'return_all': False}
  368. res = optimize.minimize(self.func, self.startparams, args=(),
  369. method='Nelder-mead', options=opts)
  370. params, fopt, numiter, func_calls, warnflag = (
  371. res['x'], res['fun'], res['nit'], res['nfev'],
  372. res['status'])
  373. else:
  374. retval = optimize.fmin(self.func, self.startparams,
  375. args=(), maxiter=self.maxiter,
  376. full_output=True, disp=self.disp,
  377. retall=False)
  378. (params, fopt, numiter, func_calls, warnflag) = retval
  379. assert_allclose(self.func(params), self.func(self.solution),
  380. atol=1e-6)
  381. # Ensure that function call counts are 'known good'; these are from
  382. # SciPy 0.7.0. Don't allow them to increase.
  383. assert self.funccalls.c == 167, self.funccalls.c
  384. assert self.gradcalls.c == 0, self.gradcalls.c
  385. # Ensure that the function behaves the same; this is from SciPy 0.7.0
  386. assert_allclose(self.trace.t[76:78],
  387. [[0.1928968, -0.62780447, 0.35166118],
  388. [0.19572515, -0.63648426, 0.35838135]],
  389. atol=1e-14, rtol=1e-7)
  390. def test_neldermead_initial_simplex(self):
  391. # Nelder-Mead simplex algorithm
  392. simplex = np.zeros((4, 3))
  393. simplex[...] = self.startparams
  394. for j in range(3):
  395. simplex[j+1, j] += 0.1
  396. if self.use_wrapper:
  397. opts = {'maxiter': self.maxiter, 'disp': False,
  398. 'return_all': True, 'initial_simplex': simplex}
  399. res = optimize.minimize(self.func, self.startparams, args=(),
  400. method='Nelder-mead', options=opts)
  401. params, fopt, numiter, func_calls, warnflag = (res['x'],
  402. res['fun'],
  403. res['nit'],
  404. res['nfev'],
  405. res['status'])
  406. assert_allclose(res['allvecs'][0], simplex[0])
  407. else:
  408. retval = optimize.fmin(self.func, self.startparams,
  409. args=(), maxiter=self.maxiter,
  410. full_output=True, disp=False, retall=False,
  411. initial_simplex=simplex)
  412. (params, fopt, numiter, func_calls, warnflag) = retval
  413. assert_allclose(self.func(params), self.func(self.solution),
  414. atol=1e-6)
  415. # Ensure that function call counts are 'known good'; these are from
  416. # SciPy 0.17.0. Don't allow them to increase.
  417. assert self.funccalls.c == 100, self.funccalls.c
  418. assert self.gradcalls.c == 0, self.gradcalls.c
  419. # Ensure that the function behaves the same; this is from SciPy 0.15.0
  420. assert_allclose(self.trace.t[50:52],
  421. [[0.14687474, -0.5103282, 0.48252111],
  422. [0.14474003, -0.5282084, 0.48743951]],
  423. atol=1e-14, rtol=1e-7)
  424. def test_neldermead_initial_simplex_bad(self):
  425. # Check it fails with a bad simplices
  426. bad_simplices = []
  427. simplex = np.zeros((3, 2))
  428. simplex[...] = self.startparams[:2]
  429. for j in range(2):
  430. simplex[j+1, j] += 0.1
  431. bad_simplices.append(simplex)
  432. simplex = np.zeros((3, 3))
  433. bad_simplices.append(simplex)
  434. for simplex in bad_simplices:
  435. if self.use_wrapper:
  436. opts = {'maxiter': self.maxiter, 'disp': False,
  437. 'return_all': False, 'initial_simplex': simplex}
  438. assert_raises(ValueError,
  439. optimize.minimize,
  440. self.func,
  441. self.startparams,
  442. args=(),
  443. method='Nelder-mead',
  444. options=opts)
  445. else:
  446. assert_raises(ValueError, optimize.fmin,
  447. self.func, self.startparams,
  448. args=(), maxiter=self.maxiter,
  449. full_output=True, disp=False, retall=False,
  450. initial_simplex=simplex)
  451. def test_neldermead_x0_ub(self):
  452. # checks whether minimisation occurs correctly for entries where
  453. # x0 == ub
  454. # gh19991
  455. def quad(x):
  456. return np.sum(x**2)
  457. res = optimize.minimize(
  458. quad,
  459. [1],
  460. bounds=[(0, 1.)],
  461. method='nelder-mead'
  462. )
  463. assert_allclose(res.x, [0])
  464. res = optimize.minimize(
  465. quad,
  466. [1, 2],
  467. bounds=[(0, 1.), (1, 3.)],
  468. method='nelder-mead'
  469. )
  470. assert_allclose(res.x, [0, 1])
  471. def test_ncg_negative_maxiter(self):
  472. # Regression test for gh-8241
  473. opts = {'maxiter': -1}
  474. result = optimize.minimize(self.func, self.startparams,
  475. method='Newton-CG', jac=self.grad,
  476. args=(), options=opts)
  477. assert result.status == 1
  478. def test_ncg_zero_xtol(self):
  479. # Regression test for gh-20214
  480. def cosine(x):
  481. return np.cos(x[0])
  482. def jac(x):
  483. return -np.sin(x[0])
  484. x0 = [0.1]
  485. xtol = 0
  486. result = optimize.minimize(cosine,
  487. x0=x0,
  488. jac=jac,
  489. method="newton-cg",
  490. options=dict(xtol=xtol))
  491. assert result.status == 0
  492. assert_almost_equal(result.x[0], np.pi)
  493. def test_ncg(self):
  494. # line-search Newton conjugate gradient optimization routine
  495. if self.use_wrapper:
  496. opts = {'maxiter': self.maxiter, 'disp': self.disp,
  497. 'return_all': False}
  498. retval = optimize.minimize(self.func, self.startparams,
  499. method='Newton-CG', jac=self.grad,
  500. args=(), options=opts)['x']
  501. else:
  502. retval = optimize.fmin_ncg(self.func, self.startparams, self.grad,
  503. args=(), maxiter=self.maxiter,
  504. full_output=False, disp=self.disp,
  505. retall=False)
  506. params = retval
  507. assert_allclose(self.func(params), self.func(self.solution),
  508. atol=1e-6)
  509. # Ensure that function call counts are 'known good'; these are from
  510. # SciPy 0.7.0. Don't allow them to increase.
  511. assert self.funccalls.c == 7, self.funccalls.c
  512. assert self.gradcalls.c <= 22, self.gradcalls.c # 0.13.0
  513. # assert self.gradcalls <= 18, self.gradcalls # 0.9.0
  514. # assert self.gradcalls == 18, self.gradcalls # 0.8.0
  515. # assert self.gradcalls == 22, self.gradcalls # 0.7.0
  516. # Ensure that the function behaves the same; this is from SciPy 0.7.0
  517. assert_allclose(self.trace.t[3:5],
  518. [[-4.35700753e-07, -5.24869435e-01, 4.87527480e-01],
  519. [-4.35700753e-07, -5.24869401e-01, 4.87527774e-01]],
  520. atol=1e-6, rtol=1e-7)
  521. def test_ncg_hess(self):
  522. # Newton conjugate gradient with Hessian
  523. if self.use_wrapper:
  524. opts = {'maxiter': self.maxiter, 'disp': self.disp,
  525. 'return_all': False}
  526. retval = optimize.minimize(self.func, self.startparams,
  527. method='Newton-CG', jac=self.grad,
  528. hess=self.hess,
  529. args=(), options=opts)['x']
  530. else:
  531. retval = optimize.fmin_ncg(self.func, self.startparams, self.grad,
  532. fhess=self.hess,
  533. args=(), maxiter=self.maxiter,
  534. full_output=False, disp=self.disp,
  535. retall=False)
  536. params = retval
  537. assert_allclose(self.func(params), self.func(self.solution),
  538. atol=1e-6)
  539. # Ensure that function call counts are 'known good'; these are from
  540. # SciPy 0.7.0. Don't allow them to increase.
  541. assert self.funccalls.c <= 7, self.funccalls.c # gh10673
  542. assert self.gradcalls.c <= 18, self.gradcalls.c # 0.9.0
  543. # assert self.gradcalls == 18, self.gradcalls # 0.8.0
  544. # assert self.gradcalls == 22, self.gradcalls # 0.7.0
  545. # Ensure that the function behaves the same; this is from SciPy 0.7.0
  546. assert_allclose(self.trace.t[3:5],
  547. [[-4.35700753e-07, -5.24869435e-01, 4.87527480e-01],
  548. [-4.35700753e-07, -5.24869401e-01, 4.87527774e-01]],
  549. atol=1e-6, rtol=1e-7)
  550. def test_ncg_hessp(self):
  551. # Newton conjugate gradient with Hessian times a vector p.
  552. if self.use_wrapper:
  553. opts = {'maxiter': self.maxiter, 'disp': self.disp,
  554. 'return_all': False}
  555. retval = optimize.minimize(self.func, self.startparams,
  556. method='Newton-CG', jac=self.grad,
  557. hessp=self.hessp,
  558. args=(), options=opts)['x']
  559. else:
  560. retval = optimize.fmin_ncg(self.func, self.startparams, self.grad,
  561. fhess_p=self.hessp,
  562. args=(), maxiter=self.maxiter,
  563. full_output=False, disp=self.disp,
  564. retall=False)
  565. params = retval
  566. assert_allclose(self.func(params), self.func(self.solution),
  567. atol=1e-6)
  568. # Ensure that function call counts are 'known good'; these are from
  569. # SciPy 0.7.0. Don't allow them to increase.
  570. assert self.funccalls.c <= 7, self.funccalls.c # gh10673
  571. assert self.gradcalls.c <= 18, self.gradcalls.c # 0.9.0
  572. # assert self.gradcalls == 18, self.gradcalls # 0.8.0
  573. # assert self.gradcalls == 22, self.gradcalls # 0.7.0
  574. # Ensure that the function behaves the same; this is from SciPy 0.7.0
  575. assert_allclose(self.trace.t[3:5],
  576. [[-4.35700753e-07, -5.24869435e-01, 4.87527480e-01],
  577. [-4.35700753e-07, -5.24869401e-01, 4.87527774e-01]],
  578. atol=1e-6, rtol=1e-7)
  579. def test_cobyqa(self):
  580. # COBYQA method.
  581. if self.use_wrapper:
  582. res = optimize.minimize(
  583. self.func,
  584. self.startparams,
  585. method='cobyqa',
  586. options={'maxiter': self.maxiter, 'disp': self.disp},
  587. )
  588. assert_allclose(res.fun, self.func(self.solution), atol=1e-6)
  589. # Ensure that function call counts are 'known good'; these are from
  590. # SciPy 1.14.0. Don't allow them to increase. The exact evaluation
  591. # count is sensitive to numerical error and floating-point
  592. # computations are not bit-for-bit reproducible across machines. It
  593. # takes 45 calls on my machine, but we can add the same +20 margin
  594. # as is used in `test_powell`
  595. assert self.funccalls.c <= 45 + 20, self.funccalls.c
  596. def test_maxfev_test():
  597. rng = np.random.default_rng(271707100830272976862395227613146332411)
  598. def cost(x):
  599. return rng.random(1) * 1000 # never converged problem
  600. for imaxfev in [1, 10, 50]:
  601. # "TNC" and "L-BFGS-B" also supports max function evaluation, but
  602. # these may violate the limit because of evaluating gradients
  603. # by numerical differentiation. See the discussion in PR #14805.
  604. for method in ['Powell', 'Nelder-Mead']:
  605. result = optimize.minimize(cost, rng.random(10),
  606. method=method,
  607. options={'maxfev': imaxfev})
  608. assert result["nfev"] == imaxfev
  609. def test_wrap_scalar_function_with_validation():
  610. def func_(x):
  611. return x
  612. fcalls, func = optimize._optimize.\
  613. _wrap_scalar_function_maxfun_validation(func_, np.asarray(1), 5)
  614. for i in range(5):
  615. func(np.asarray(i))
  616. assert fcalls[0] == i+1
  617. msg = "Too many function calls"
  618. with assert_raises(optimize._optimize._MaxFuncCallError, match=msg):
  619. func(np.asarray(i)) # exceeded maximum function call
  620. fcalls, func = optimize._optimize.\
  621. _wrap_scalar_function_maxfun_validation(func_, np.asarray(1), 5)
  622. msg = "The user-provided objective function must return a scalar value."
  623. with assert_raises(ValueError, match=msg):
  624. func(np.array([1, 1]))
  625. def test_obj_func_returns_scalar():
  626. match = ("The user-provided "
  627. "objective function must "
  628. "return a scalar value.")
  629. with assert_raises(ValueError, match=match):
  630. optimize.minimize(lambda x: x, np.array([1, 1]), method='BFGS')
  631. def test_neldermead_iteration_num():
  632. x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])
  633. res = optimize._minimize._minimize_neldermead(optimize.rosen, x0,
  634. xatol=1e-8)
  635. assert res.nit <= 339
  636. def test_neldermead_respect_fp():
  637. # Nelder-Mead should respect the fp type of the input + function
  638. x0 = np.array([5.0, 4.0]).astype(np.float32)
  639. def rosen_(x):
  640. assert x.dtype == np.float32
  641. return optimize.rosen(x)
  642. optimize.minimize(rosen_, x0, method='Nelder-Mead')
  643. def test_neldermead_xatol_fatol():
  644. # gh4484
  645. # test we can call with fatol, xatol specified
  646. def func(x):
  647. return x[0] ** 2 + x[1] ** 2
  648. optimize._minimize._minimize_neldermead(func, [1, 1], maxiter=2,
  649. xatol=1e-3, fatol=1e-3)
  650. def test_neldermead_adaptive():
  651. def func(x):
  652. return np.sum(x ** 2)
  653. p0 = [0.15746215, 0.48087031, 0.44519198, 0.4223638, 0.61505159,
  654. 0.32308456, 0.9692297, 0.4471682, 0.77411992, 0.80441652,
  655. 0.35994957, 0.75487856, 0.99973421, 0.65063887, 0.09626474]
  656. res = optimize.minimize(func, p0, method='Nelder-Mead')
  657. assert_equal(res.success, False)
  658. res = optimize.minimize(func, p0, method='Nelder-Mead',
  659. options={'adaptive': True})
  660. assert_equal(res.success, True)
  661. def test_bounded_powell_outsidebounds():
  662. # With the bounded Powell method if you start outside the bounds the final
  663. # should still be within the bounds (provided that the user doesn't make a
  664. # bad choice for the `direc` argument).
  665. def func(x):
  666. return np.sum(x ** 2)
  667. bounds = (-1, 1), (-1, 1), (-1, 1)
  668. x0 = [-4, .5, -.8]
  669. # we're starting outside the bounds, so we should get a warning
  670. with pytest.warns(optimize.OptimizeWarning):
  671. res = optimize.minimize(func, x0, bounds=bounds, method="Powell")
  672. assert_allclose(res.x, np.array([0.] * len(x0)), atol=1e-6)
  673. assert_equal(res.success, True)
  674. assert_equal(res.status, 0)
  675. # However, now if we change the `direc` argument such that the
  676. # set of vectors does not span the parameter space, then we may
  677. # not end up back within the bounds. Here we see that the first
  678. # parameter cannot be updated!
  679. direc = [[0, 0, 0], [0, 1, 0], [0, 0, 1]]
  680. # we're starting outside the bounds, so we should get a warning
  681. with pytest.warns(optimize.OptimizeWarning):
  682. res = optimize.minimize(func, x0,
  683. bounds=bounds, method="Powell",
  684. options={'direc': direc})
  685. assert_allclose(res.x, np.array([-4., 0, 0]), atol=1e-6)
  686. assert_equal(res.success, False)
  687. assert_equal(res.status, 4)
  688. def test_bounded_powell_vs_powell():
  689. # here we test an example where the bounded Powell method
  690. # will return a different result than the standard Powell
  691. # method.
  692. # first we test a simple example where the minimum is at
  693. # the origin and the minimum that is within the bounds is
  694. # larger than the minimum at the origin.
  695. def func(x):
  696. return np.sum(x ** 2)
  697. bounds = (-5, -1), (-10, -0.1), (1, 9.2), (-4, 7.6), (-15.9, -2)
  698. x0 = [-2.1, -5.2, 1.9, 0, -2]
  699. options = {'ftol': 1e-10, 'xtol': 1e-10}
  700. res_powell = optimize.minimize(func, x0, method="Powell", options=options)
  701. assert_allclose(res_powell.x, 0., atol=1e-6)
  702. assert_allclose(res_powell.fun, 0., atol=1e-6)
  703. res_bounded_powell = optimize.minimize(func, x0, options=options,
  704. bounds=bounds,
  705. method="Powell")
  706. p = np.array([-1, -0.1, 1, 0, -2])
  707. assert_allclose(res_bounded_powell.x, p, atol=1e-6)
  708. assert_allclose(res_bounded_powell.fun, func(p), atol=1e-6)
  709. # now we test bounded Powell but with a mix of inf bounds.
  710. bounds = (None, -1), (-np.inf, -.1), (1, np.inf), (-4, None), (-15.9, -2)
  711. res_bounded_powell = optimize.minimize(func, x0, options=options,
  712. bounds=bounds,
  713. method="Powell")
  714. p = np.array([-1, -0.1, 1, 0, -2])
  715. assert_allclose(res_bounded_powell.x, p, atol=1e-6)
  716. assert_allclose(res_bounded_powell.fun, func(p), atol=1e-6)
  717. # next we test an example where the global minimum is within
  718. # the bounds, but the bounded Powell method performs better
  719. # than the standard Powell method.
  720. def func(x):
  721. t = np.sin(-x[0]) * np.cos(x[1]) * np.sin(-x[0] * x[1]) * np.cos(x[1])
  722. t -= np.cos(np.sin(x[1] * x[2]) * np.cos(x[2]))
  723. return t**2
  724. bounds = [(-2, 5)] * 3
  725. x0 = [-0.5, -0.5, -0.5]
  726. res_powell = optimize.minimize(func, x0, method="Powell")
  727. res_bounded_powell = optimize.minimize(func, x0,
  728. bounds=bounds,
  729. method="Powell")
  730. assert_allclose(res_powell.fun, 0.007136253919761627, atol=1e-6)
  731. assert_allclose(res_bounded_powell.fun, 0, atol=1e-6)
  732. # next we test the previous example where the we provide Powell
  733. # with (-inf, inf) bounds, and compare it to providing Powell
  734. # with no bounds. They should end up the same.
  735. bounds = [(-np.inf, np.inf)] * 3
  736. res_bounded_powell = optimize.minimize(func, x0,
  737. bounds=bounds,
  738. method="Powell")
  739. assert_allclose(res_powell.fun, res_bounded_powell.fun, atol=1e-6)
  740. assert_allclose(res_powell.nfev, res_bounded_powell.nfev, atol=1e-6)
  741. assert_allclose(res_powell.x, res_bounded_powell.x, atol=1e-6)
  742. # now test when x0 starts outside of the bounds.
  743. x0 = [45.46254415, -26.52351498, 31.74830248]
  744. bounds = [(-2, 5)] * 3
  745. # we're starting outside the bounds, so we should get a warning
  746. with pytest.warns(optimize.OptimizeWarning):
  747. res_bounded_powell = optimize.minimize(func, x0,
  748. bounds=bounds,
  749. method="Powell")
  750. assert_allclose(res_bounded_powell.fun, 0, atol=1e-6)
  751. def test_onesided_bounded_powell_stability():
  752. # When the Powell method is bounded on only one side, a
  753. # np.tan transform is done in order to convert it into a
  754. # completely bounded problem. Here we do some simple tests
  755. # of one-sided bounded Powell where the optimal solutions
  756. # are large to test the stability of the transformation.
  757. kwargs = {'method': 'Powell',
  758. 'bounds': [(-np.inf, 1e6)] * 3,
  759. 'options': {'ftol': 1e-8, 'xtol': 1e-8}}
  760. x0 = [1, 1, 1]
  761. # df/dx is constant.
  762. def f(x):
  763. return -np.sum(x)
  764. res = optimize.minimize(f, x0, **kwargs)
  765. assert_allclose(res.fun, -3e6, atol=1e-4)
  766. # df/dx gets smaller and smaller.
  767. def f(x):
  768. return -np.abs(np.sum(x)) ** (0.1) * (1 if np.all(x > 0) else -1)
  769. res = optimize.minimize(f, x0, **kwargs)
  770. assert_allclose(res.fun, -(3e6) ** (0.1))
  771. # df/dx gets larger and larger.
  772. def f(x):
  773. return -np.abs(np.sum(x)) ** 10 * (1 if np.all(x > 0) else -1)
  774. res = optimize.minimize(f, x0, **kwargs)
  775. assert_allclose(res.fun, -(3e6) ** 10, rtol=1e-7)
  776. # df/dx gets larger for some of the variables and smaller for others.
  777. def f(x):
  778. t = -np.abs(np.sum(x[:2])) ** 5 - np.abs(np.sum(x[2:])) ** (0.1)
  779. t *= (1 if np.all(x > 0) else -1)
  780. return t
  781. kwargs['bounds'] = [(-np.inf, 1e3)] * 3
  782. res = optimize.minimize(f, x0, **kwargs)
  783. assert_allclose(res.fun, -(2e3) ** 5 - (1e6) ** (0.1), rtol=1e-7)
  784. class TestOptimizeWrapperDisp(CheckOptimizeParameterized):
  785. use_wrapper = True
  786. disp = True
  787. class TestOptimizeWrapperNoDisp(CheckOptimizeParameterized):
  788. use_wrapper = True
  789. disp = False
  790. class TestOptimizeNoWrapperDisp(CheckOptimizeParameterized):
  791. use_wrapper = False
  792. disp = True
  793. class TestOptimizeNoWrapperNoDisp(CheckOptimizeParameterized):
  794. use_wrapper = False
  795. disp = False
  796. class TestOptimizeSimple(CheckOptimize):
  797. def test_bfgs_nan(self):
  798. # Test corner case where nan is fed to optimizer. See gh-2067.
  799. def func(x):
  800. return x
  801. def fprime(x):
  802. return np.ones_like(x)
  803. x0 = [np.nan]
  804. with np.errstate(over='ignore', invalid='ignore'):
  805. x = optimize.fmin_bfgs(func, x0, fprime, disp=False)
  806. assert np.isnan(func(x))
  807. def test_bfgs_nan_return(self):
  808. # Test corner cases where fun returns NaN. See gh-4793.
  809. # First case: NaN from first call.
  810. def func(x):
  811. return np.nan
  812. with np.errstate(invalid='ignore'):
  813. result = optimize.minimize(func, 0)
  814. assert np.isnan(result['fun'])
  815. assert result['success'] is False
  816. # Second case: NaN from second call.
  817. def func(x):
  818. return 0 if x == 0 else np.nan
  819. def fprime(x):
  820. return np.ones_like(x) # Steer away from zero.
  821. with np.errstate(invalid='ignore'):
  822. result = optimize.minimize(func, 0, jac=fprime)
  823. assert np.isnan(result['fun'])
  824. assert result['success'] is False
  825. def test_bfgs_numerical_jacobian(self):
  826. # BFGS with numerical Jacobian and a vector epsilon parameter.
  827. # define the epsilon parameter using a random vector
  828. rng = np.random.default_rng(1234)
  829. epsilon = np.sqrt(np.spacing(1.)) * rng.random(len(self.solution))
  830. params = optimize.fmin_bfgs(self.func, self.startparams,
  831. epsilon=epsilon, args=(),
  832. maxiter=self.maxiter, disp=False)
  833. assert_allclose(self.func(params), self.func(self.solution),
  834. atol=1e-6)
  835. def test_finite_differences_jac(self):
  836. methods = ['BFGS', 'CG', 'TNC']
  837. jacs = ['2-point', '3-point', None]
  838. for method, jac in itertools.product(methods, jacs):
  839. result = optimize.minimize(self.func, self.startparams,
  840. method=method, jac=jac)
  841. assert_allclose(self.func(result.x), self.func(self.solution),
  842. atol=1e-6)
  843. def test_finite_differences_hess(self):
  844. # test that all the methods that require hess can use finite-difference
  845. # For Newton-CG, trust-ncg, trust-krylov the FD estimated hessian is
  846. # wrapped in a hessp function
  847. # dogleg, trust-exact actually require true hessians at the moment, so
  848. # they're excluded.
  849. methods = ['trust-constr', 'Newton-CG', 'trust-ncg', 'trust-krylov']
  850. hesses = FD_METHODS + (optimize.BFGS,)
  851. for method, hess in itertools.product(methods, hesses):
  852. if hess is optimize.BFGS:
  853. hess = hess()
  854. result = optimize.minimize(self.func, self.startparams,
  855. method=method, jac=self.grad,
  856. hess=hess)
  857. assert result.success
  858. # check that the methods demand some sort of Hessian specification
  859. # Newton-CG creates its own hessp, and trust-constr doesn't need a hess
  860. # specified either
  861. methods = ['trust-ncg', 'trust-krylov', 'dogleg', 'trust-exact']
  862. for method in methods:
  863. with pytest.raises(ValueError):
  864. optimize.minimize(self.func, self.startparams,
  865. method=method, jac=self.grad,
  866. hess=None)
  867. def test_bfgs_gh_2169(self):
  868. def f(x):
  869. if x < 0:
  870. return 1.79769313e+308
  871. else:
  872. return x + 1./x
  873. xs = optimize.fmin_bfgs(f, [10.], disp=False)
  874. assert_allclose(xs, 1.0, rtol=1e-4, atol=1e-4)
  875. def test_bfgs_double_evaluations(self):
  876. # check BFGS does not evaluate twice in a row at same point
  877. def f(x):
  878. xp = x[0]
  879. assert xp not in seen
  880. seen.add(xp)
  881. return 10*x**2, 20*x
  882. seen = set()
  883. optimize.minimize(f, -100, method='bfgs', jac=True, tol=1e-7)
  884. def test_l_bfgs_b(self):
  885. # limited-memory bound-constrained BFGS algorithm
  886. retval = optimize.fmin_l_bfgs_b(self.func, self.startparams,
  887. self.grad, args=(),
  888. maxiter=self.maxiter)
  889. (params, fopt, d) = retval
  890. assert_allclose(self.func(params), self.func(self.solution),
  891. atol=1e-6)
  892. # Ensure that function call counts are 'known good'; these are from
  893. # SciPy 0.7.0. Don't allow them to increase.
  894. assert self.funccalls.c == 7, self.funccalls.c
  895. assert self.gradcalls.c == 5, self.gradcalls.c
  896. # Ensure that the function behaves the same; this is from SciPy 0.7.0
  897. # test fixed in gh10673
  898. assert_allclose(self.trace.t[3:5],
  899. [[8.117083e-16, -5.196198e-01, 4.897617e-01],
  900. [0., -0.52489628, 0.48753042]],
  901. atol=1e-14, rtol=1e-7)
  902. def test_l_bfgs_b_numjac(self):
  903. # L-BFGS-B with numerical Jacobian
  904. retval = optimize.fmin_l_bfgs_b(self.func, self.startparams,
  905. approx_grad=True,
  906. maxiter=self.maxiter)
  907. (params, fopt, d) = retval
  908. assert_allclose(self.func(params), self.func(self.solution),
  909. atol=1e-6)
  910. def test_l_bfgs_b_funjac(self):
  911. # L-BFGS-B with combined objective function and Jacobian
  912. def fun(x):
  913. return self.func(x), self.grad(x)
  914. retval = optimize.fmin_l_bfgs_b(fun, self.startparams,
  915. maxiter=self.maxiter)
  916. (params, fopt, d) = retval
  917. assert_allclose(self.func(params), self.func(self.solution),
  918. atol=1e-6)
  919. def test_l_bfgs_b_maxiter(self):
  920. # gh7854
  921. # Ensure that not more than maxiters are ever run.
  922. class Callback:
  923. def __init__(self):
  924. self.nit = 0
  925. self.fun = None
  926. self.x = None
  927. def __call__(self, x):
  928. self.x = x
  929. self.fun = optimize.rosen(x)
  930. self.nit += 1
  931. c = Callback()
  932. res = optimize.minimize(optimize.rosen, [0., 0.], method='l-bfgs-b',
  933. callback=c, options={'maxiter': 5})
  934. assert_equal(res.nit, 5)
  935. assert_almost_equal(res.x, c.x)
  936. assert_almost_equal(res.fun, c.fun)
  937. assert_equal(res.status, 1)
  938. assert res.success is False
  939. assert_equal(res.message,
  940. 'STOP: TOTAL NO. OF ITERATIONS REACHED LIMIT')
  941. def test_minimize_l_bfgs_b(self):
  942. # Minimize with L-BFGS-B method
  943. opts = {'maxiter': self.maxiter}
  944. r = optimize.minimize(self.func, self.startparams,
  945. method='L-BFGS-B', jac=self.grad,
  946. options=opts)
  947. assert_allclose(self.func(r.x), self.func(self.solution),
  948. atol=1e-6)
  949. assert self.gradcalls.c == r.njev
  950. self.funccalls.c = self.gradcalls.c = 0
  951. # approximate jacobian
  952. ra = optimize.minimize(self.func, self.startparams,
  953. method='L-BFGS-B', options=opts)
  954. # check that function evaluations in approximate jacobian are counted
  955. # assert_(ra.nfev > r.nfev)
  956. assert self.funccalls.c == ra.nfev
  957. assert_allclose(self.func(ra.x), self.func(self.solution),
  958. atol=1e-6)
  959. self.funccalls.c = self.gradcalls.c = 0
  960. # approximate jacobian
  961. ra = optimize.minimize(self.func, self.startparams, jac='3-point',
  962. method='L-BFGS-B', options=opts)
  963. assert self.funccalls.c == ra.nfev
  964. assert_allclose(self.func(ra.x), self.func(self.solution),
  965. atol=1e-6)
  966. def test_minimize_l_bfgs_b_ftol(self):
  967. # Check that the `ftol` parameter in l_bfgs_b works as expected
  968. v0 = None
  969. for tol in [1e-1, 1e-4, 1e-7, 1e-10]:
  970. opts = {'maxiter': self.maxiter, 'ftol': tol}
  971. sol = optimize.minimize(self.func, self.startparams,
  972. method='L-BFGS-B', jac=self.grad,
  973. options=opts)
  974. v = self.func(sol.x)
  975. if v0 is None:
  976. v0 = v
  977. else:
  978. assert v < v0
  979. assert_allclose(v, self.func(self.solution), rtol=tol)
  980. def test_minimize_l_bfgs_maxls(self):
  981. # check that the maxls is passed down to the Fortran routine
  982. sol = optimize.minimize(optimize.rosen, np.array([-1.2, 1.0]),
  983. method='L-BFGS-B', jac=optimize.rosen_der,
  984. options={'maxls': 1})
  985. assert not sol.success
  986. def test_minimize_l_bfgs_b_maxfun_interruption(self):
  987. # gh-6162
  988. f = optimize.rosen
  989. g = optimize.rosen_der
  990. values = []
  991. x0 = np.full(7, 1000)
  992. def objfun(x):
  993. value = f(x)
  994. values.append(value)
  995. return value
  996. # Look for an interesting test case.
  997. # Request a maxfun that stops at a particularly bad function
  998. # evaluation somewhere between 100 and 300 evaluations.
  999. low, medium, high = 30, 100, 300
  1000. optimize.fmin_l_bfgs_b(objfun, x0, fprime=g, maxfun=high)
  1001. v, k = max((y, i) for i, y in enumerate(values[medium:]))
  1002. maxfun = medium + k
  1003. # If the minimization strategy is reasonable,
  1004. # the minimize() result should not be worse than the best
  1005. # of the first 30 function evaluations.
  1006. target = min(values[:low])
  1007. xmin, fmin, d = optimize.fmin_l_bfgs_b(f, x0, fprime=g, maxfun=maxfun)
  1008. assert_array_less(fmin, target)
  1009. def test_custom(self):
  1010. # This function comes from the documentation example.
  1011. def custmin(fun, x0, args=(), maxfev=None, stepsize=0.1,
  1012. maxiter=100, callback=None, **options):
  1013. bestx = x0
  1014. besty = fun(x0)
  1015. funcalls = 1
  1016. niter = 0
  1017. improved = True
  1018. stop = False
  1019. while improved and not stop and niter < maxiter:
  1020. improved = False
  1021. niter += 1
  1022. for dim in range(np.size(x0)):
  1023. for s in [bestx[dim] - stepsize, bestx[dim] + stepsize]:
  1024. testx = np.copy(bestx)
  1025. testx[dim] = s
  1026. testy = fun(testx, *args)
  1027. funcalls += 1
  1028. if testy < besty:
  1029. besty = testy
  1030. bestx = testx
  1031. improved = True
  1032. if callback is not None:
  1033. callback(bestx)
  1034. if maxfev is not None and funcalls >= maxfev:
  1035. stop = True
  1036. break
  1037. return optimize.OptimizeResult(fun=besty, x=bestx, nit=niter,
  1038. nfev=funcalls, success=(niter > 1))
  1039. x0 = [1.35, 0.9, 0.8, 1.1, 1.2]
  1040. res = optimize.minimize(optimize.rosen, x0, method=custmin,
  1041. options=dict(stepsize=0.05))
  1042. assert_allclose(res.x, 1.0, rtol=1e-4, atol=1e-4)
  1043. def test_gh10771(self):
  1044. # check that minimize passes bounds and constraints to a custom
  1045. # minimizer without altering them.
  1046. bounds = [(-2, 2), (0, 3)]
  1047. constraints = 'constraints'
  1048. def custmin(fun, x0, **options):
  1049. assert options['bounds'] is bounds
  1050. assert options['constraints'] is constraints
  1051. return optimize.OptimizeResult()
  1052. x0 = [1, 1]
  1053. optimize.minimize(optimize.rosen, x0, method=custmin,
  1054. bounds=bounds, constraints=constraints)
  1055. def test_minimize_tol_parameter(self):
  1056. # Check that the minimize() tol= argument does something
  1057. def func(z):
  1058. x, y = z
  1059. return x**2*y**2 + x**4 + 1
  1060. def dfunc(z):
  1061. x, y = z
  1062. return np.array([2*x*y**2 + 4*x**3, 2*x**2*y])
  1063. for method in ['nelder-mead', 'powell', 'cg', 'bfgs',
  1064. 'newton-cg', 'l-bfgs-b', 'tnc',
  1065. 'cobyla', 'cobyqa', 'slsqp']:
  1066. if method in ('nelder-mead', 'powell', 'cobyla', 'cobyqa'):
  1067. jac = None
  1068. else:
  1069. jac = dfunc
  1070. sol1 = optimize.minimize(func, [2, 2], jac=jac, tol=1e-10,
  1071. method=method)
  1072. sol2 = optimize.minimize(func, [2, 2], jac=jac, tol=1.0,
  1073. method=method)
  1074. assert func(sol1.x) < func(sol2.x), \
  1075. f"{method}: {func(sol1.x)} vs. {func(sol2.x)}"
  1076. @pytest.mark.fail_slow(10)
  1077. @pytest.mark.filterwarnings('ignore::UserWarning')
  1078. @pytest.mark.filterwarnings('ignore::RuntimeWarning') # See gh-18547
  1079. @pytest.mark.parametrize('method',
  1080. ['fmin', 'fmin_powell', 'fmin_cg', 'fmin_bfgs',
  1081. 'fmin_ncg', 'fmin_l_bfgs_b', 'fmin_tnc',
  1082. 'fmin_slsqp'] + MINIMIZE_METHODS)
  1083. def test_minimize_callback_copies_array(self, method):
  1084. # Check that arrays passed to callbacks are not modified
  1085. # inplace by the optimizer afterward
  1086. if method in ('fmin_tnc', 'fmin_l_bfgs_b'):
  1087. def func(x):
  1088. return optimize.rosen(x), optimize.rosen_der(x)
  1089. else:
  1090. func = optimize.rosen
  1091. jac = optimize.rosen_der
  1092. hess = optimize.rosen_hess
  1093. x0 = np.zeros(10)
  1094. # Set options
  1095. kwargs = {}
  1096. if method.startswith('fmin'):
  1097. routine = getattr(optimize, method)
  1098. if method == 'fmin_slsqp':
  1099. kwargs['iter'] = 5
  1100. elif method == 'fmin_tnc':
  1101. kwargs['maxfun'] = 100
  1102. elif method in ('fmin', 'fmin_powell'):
  1103. kwargs['maxiter'] = 3500
  1104. else:
  1105. kwargs['maxiter'] = 5
  1106. else:
  1107. def routine(*a, **kw):
  1108. kw['method'] = method
  1109. return optimize.minimize(*a, **kw)
  1110. if method == 'tnc':
  1111. kwargs['options'] = dict(maxfun=100)
  1112. elif method == 'cobyla':
  1113. kwargs['options'] = dict(maxiter=100)
  1114. else:
  1115. kwargs['options'] = dict(maxiter=5)
  1116. if method in ('fmin_ncg',):
  1117. kwargs['fprime'] = jac
  1118. elif method in ('newton-cg',):
  1119. kwargs['jac'] = jac
  1120. elif method in ('trust-krylov', 'trust-exact', 'trust-ncg', 'dogleg',
  1121. 'trust-constr'):
  1122. kwargs['jac'] = jac
  1123. kwargs['hess'] = hess
  1124. # Run with callback
  1125. results = []
  1126. def callback(x, *args, **kwargs):
  1127. assert not isinstance(x, optimize.OptimizeResult)
  1128. results.append((x, np.copy(x)))
  1129. routine(func, x0, callback=callback, **kwargs)
  1130. # Check returned arrays coincide with their copies
  1131. # and have no memory overlap
  1132. assert len(results) > 2
  1133. assert all(np.all(x == y) for x, y in results)
  1134. combinations = itertools.combinations(results, 2)
  1135. assert not any(np.may_share_memory(x[0], y[0]) for x, y in combinations)
  1136. @pytest.mark.parametrize('method', ['nelder-mead', 'powell', 'cg',
  1137. 'bfgs', 'newton-cg', 'l-bfgs-b',
  1138. 'tnc', 'cobyla', 'cobyqa', 'slsqp'])
  1139. def test_no_increase(self, method):
  1140. # Check that the solver doesn't return a value worse than the
  1141. # initial point.
  1142. def func(x):
  1143. return (x - 1)**2
  1144. def bad_grad(x):
  1145. # purposefully invalid gradient function, simulates a case
  1146. # where line searches start failing
  1147. return 2*(x - 1) * (-1) - 2
  1148. x0 = np.array([2.0])
  1149. f0 = func(x0)
  1150. jac = bad_grad
  1151. options = dict(maxfun=20) if method == 'tnc' else dict(maxiter=20)
  1152. if method in ['nelder-mead', 'powell', 'cobyla', 'cobyqa']:
  1153. jac = None
  1154. sol = optimize.minimize(func, x0, jac=jac, method=method,
  1155. options=options)
  1156. assert_equal(func(sol.x), sol.fun)
  1157. if method == 'slsqp':
  1158. pytest.xfail("SLSQP returns slightly worse")
  1159. assert func(sol.x) <= f0
  1160. def test_slsqp_respect_bounds(self):
  1161. # Regression test for gh-3108
  1162. def f(x):
  1163. return sum((x - np.array([1., 2., 3., 4.]))**2)
  1164. def cons(x):
  1165. a = np.array([[-1, -1, -1, -1], [-3, -3, -2, -1]])
  1166. return np.concatenate([np.dot(a, x) + np.array([5, 10]), x])
  1167. x0 = np.array([0.5, 1., 1.5, 2.])
  1168. res = optimize.minimize(f, x0, method='slsqp',
  1169. constraints={'type': 'ineq', 'fun': cons})
  1170. assert_allclose(res.x, np.array([0., 2, 5, 8])/3, atol=1e-12)
  1171. @pytest.mark.parametrize('method', ['Nelder-Mead', 'Powell', 'CG', 'BFGS',
  1172. 'Newton-CG', 'L-BFGS-B', 'SLSQP',
  1173. 'trust-constr', 'dogleg', 'trust-ncg',
  1174. 'trust-exact', 'trust-krylov',
  1175. 'cobyqa'])
  1176. def test_respect_maxiter(self, method):
  1177. # Check that the number of iterations equals max_iter, assuming
  1178. # convergence doesn't establish before
  1179. MAXITER = 4
  1180. x0 = np.zeros(10)
  1181. sf = ScalarFunction(optimize.rosen, x0, (), optimize.rosen_der,
  1182. optimize.rosen_hess, None, None)
  1183. # Set options
  1184. kwargs = {'method': method, 'options': dict(maxiter=MAXITER)}
  1185. if method in ('Newton-CG',):
  1186. kwargs['jac'] = sf.grad
  1187. elif method in ('trust-krylov', 'trust-exact', 'trust-ncg', 'dogleg',
  1188. 'trust-constr'):
  1189. kwargs['jac'] = sf.grad
  1190. kwargs['hess'] = sf.hess
  1191. sol = optimize.minimize(sf.fun, x0, **kwargs)
  1192. assert sol.nit == MAXITER
  1193. assert sol.nfev >= sf.nfev
  1194. if hasattr(sol, 'njev'):
  1195. assert sol.njev >= sf.ngev
  1196. # method specific tests
  1197. if method == 'SLSQP':
  1198. assert sol.status == 9 # Iteration limit reached
  1199. elif method == 'cobyqa':
  1200. assert sol.status == 6 # Iteration limit reached
  1201. @pytest.mark.parametrize('method', ['Nelder-Mead', 'Powell',
  1202. 'fmin', 'fmin_powell'])
  1203. def test_runtime_warning(self, method):
  1204. x0 = np.zeros(10)
  1205. sf = ScalarFunction(optimize.rosen, x0, (), optimize.rosen_der,
  1206. optimize.rosen_hess, None, None)
  1207. options = {"maxiter": 1, "disp": True}
  1208. with pytest.warns(RuntimeWarning,
  1209. match=r'Maximum number of iterations'):
  1210. if method.startswith('fmin'):
  1211. routine = getattr(optimize, method)
  1212. routine(sf.fun, x0, **options)
  1213. else:
  1214. optimize.minimize(sf.fun, x0, method=method, options=options)
  1215. def test_respect_maxiter_trust_constr_ineq_constraints(self):
  1216. # special case of minimization with trust-constr and inequality
  1217. # constraints to check maxiter limit is obeyed when using internal
  1218. # method 'tr_interior_point'
  1219. MAXITER = 4
  1220. f = optimize.rosen
  1221. jac = optimize.rosen_der
  1222. hess = optimize.rosen_hess
  1223. def fun(x):
  1224. return np.array([0.2 * x[0] - 0.4 * x[1] - 0.33 * x[2]])
  1225. cons = ({'type': 'ineq',
  1226. 'fun': fun},)
  1227. x0 = np.zeros(10)
  1228. sol = optimize.minimize(f, x0, constraints=cons, jac=jac, hess=hess,
  1229. method='trust-constr',
  1230. options=dict(maxiter=MAXITER))
  1231. assert sol.nit == MAXITER
  1232. def test_minimize_automethod(self):
  1233. def f(x):
  1234. return x**2
  1235. def cons(x):
  1236. return x - 2
  1237. x0 = np.array([10.])
  1238. sol_0 = optimize.minimize(f, x0)
  1239. sol_1 = optimize.minimize(f, x0, constraints=[{'type': 'ineq',
  1240. 'fun': cons}])
  1241. sol_2 = optimize.minimize(f, x0, bounds=[(5, 10)])
  1242. sol_3 = optimize.minimize(f, x0,
  1243. constraints=[{'type': 'ineq', 'fun': cons}],
  1244. bounds=[(5, 10)])
  1245. sol_4 = optimize.minimize(f, x0,
  1246. constraints=[{'type': 'ineq', 'fun': cons}],
  1247. bounds=[(1, 10)])
  1248. for sol in [sol_0, sol_1, sol_2, sol_3, sol_4]:
  1249. assert sol.success
  1250. assert_allclose(sol_0.x, 0, atol=1e-7)
  1251. assert_allclose(sol_1.x, 2, atol=1e-7)
  1252. assert_allclose(sol_2.x, 5, atol=1e-7)
  1253. assert_allclose(sol_3.x, 5, atol=1e-7)
  1254. assert_allclose(sol_4.x, 2, atol=1e-7)
  1255. def test_minimize_coerce_args_param(self):
  1256. # Regression test for gh-3503
  1257. def Y(x, c):
  1258. return np.sum((x-c)**2)
  1259. def dY_dx(x, c=None):
  1260. return 2*(x-c)
  1261. c = np.array([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5])
  1262. rng = np.random.default_rng(1234)
  1263. xinit = rng.standard_normal(len(c))
  1264. optimize.minimize(Y, xinit, jac=dY_dx, args=(c), method="BFGS")
  1265. def test_initial_step_scaling(self):
  1266. # Check that optimizer initial step is not huge even if the
  1267. # function and gradients are
  1268. scales = [1e-50, 1, 1e50]
  1269. methods = ['CG', 'BFGS', 'L-BFGS-B', 'Newton-CG']
  1270. def f(x):
  1271. if first_step_size[0] is None and x[0] != x0[0]:
  1272. first_step_size[0] = abs(x[0] - x0[0])
  1273. if abs(x).max() > 1e4:
  1274. raise AssertionError("Optimization stepped far away!")
  1275. return scale*(x[0] - 1)**2
  1276. def g(x):
  1277. return np.array([scale*(x[0] - 1)])
  1278. for scale, method in itertools.product(scales, methods):
  1279. if method in ('CG', 'BFGS'):
  1280. options = dict(gtol=scale*1e-8)
  1281. else:
  1282. options = dict()
  1283. if scale < 1e-10 and method in ('L-BFGS-B', 'Newton-CG'):
  1284. # XXX: return initial point if they see small gradient
  1285. continue
  1286. x0 = [-1.0]
  1287. first_step_size = [None]
  1288. res = optimize.minimize(f, x0, jac=g, method=method,
  1289. options=options)
  1290. err_msg = f"{method} {scale}: {first_step_size}: {res}"
  1291. assert res.success, err_msg
  1292. assert_allclose(res.x, [1.0], err_msg=err_msg)
  1293. assert res.nit <= 3, err_msg
  1294. if scale > 1e-10:
  1295. if method in ('CG', 'BFGS'):
  1296. assert_allclose(first_step_size[0], 1.01, err_msg=err_msg)
  1297. else:
  1298. # Newton-CG and L-BFGS-B use different logic for the first
  1299. # step, but are both scaling invariant with step sizes ~ 1
  1300. assert first_step_size[0] > 0.5 and first_step_size[0] < 3, err_msg
  1301. else:
  1302. # step size has upper bound of ||grad||, so line
  1303. # search makes many small steps
  1304. pass
  1305. @pytest.mark.parametrize('method', ['nelder-mead', 'powell', 'cg', 'bfgs',
  1306. 'newton-cg', 'l-bfgs-b', 'tnc',
  1307. 'cobyqa', 'slsqp',
  1308. 'trust-constr', 'dogleg', 'trust-ncg',
  1309. 'trust-exact', 'trust-krylov'])
  1310. def test_nan_values(self, method):
  1311. # Check nan values result to failed exit status
  1312. # test is dependent on exact seed
  1313. rng = np.random.default_rng(123122)
  1314. count = [0]
  1315. def func(x):
  1316. return np.nan
  1317. def func2(x):
  1318. count[0] += 1
  1319. if count[0] > 2:
  1320. return np.nan
  1321. else:
  1322. return rng.random()
  1323. def grad(x):
  1324. return np.array([1.0])
  1325. def hess(x):
  1326. return np.array([[1.0]])
  1327. x0 = np.array([1.0])
  1328. needs_grad = method in ('newton-cg', 'trust-krylov', 'trust-exact',
  1329. 'trust-ncg', 'dogleg')
  1330. needs_hess = method in ('trust-krylov', 'trust-exact', 'trust-ncg',
  1331. 'dogleg')
  1332. funcs = [func, func2]
  1333. grads = [grad] if needs_grad else [grad, None]
  1334. hesss = [hess] if needs_hess else [hess, None]
  1335. options = dict(maxfun=20) if method == 'tnc' else dict(maxiter=20)
  1336. with np.errstate(invalid='ignore'), warnings.catch_warnings():
  1337. warnings.filterwarnings("ignore", "delta_grad == 0.*", UserWarning)
  1338. warnings.filterwarnings(
  1339. "ignore", ".*does not use Hessian.*", RuntimeWarning)
  1340. warnings.filterwarnings(
  1341. "ignore", ".*does not use gradient.*", RuntimeWarning)
  1342. for f, g, h in itertools.product(funcs, grads, hesss):
  1343. count = [0]
  1344. sol = optimize.minimize(f, x0, jac=g, hess=h, method=method,
  1345. options=options)
  1346. assert_equal(sol.success, False)
  1347. @pytest.mark.parametrize('method', ['nelder-mead', 'cg', 'bfgs',
  1348. 'l-bfgs-b', 'tnc',
  1349. 'cobyla', 'cobyqa', 'slsqp',
  1350. 'trust-constr', 'dogleg', 'trust-ncg',
  1351. 'trust-exact', 'trust-krylov'])
  1352. def test_duplicate_evaluations(self, method):
  1353. # check that there are no duplicate evaluations for any methods
  1354. jac = hess = None
  1355. if method in ('newton-cg', 'trust-krylov', 'trust-exact',
  1356. 'trust-ncg', 'dogleg'):
  1357. jac = self.grad
  1358. if method in ('trust-krylov', 'trust-exact', 'trust-ncg',
  1359. 'dogleg'):
  1360. hess = self.hess
  1361. with np.errstate(invalid='ignore'), warnings.catch_warnings():
  1362. # for trust-constr
  1363. warnings.filterwarnings("ignore", "delta_grad == 0.*", UserWarning)
  1364. optimize.minimize(self.func, self.startparams,
  1365. method=method, jac=jac, hess=hess)
  1366. for i in range(1, len(self.trace.t)):
  1367. if np.array_equal(self.trace.t[i - 1], self.trace.t[i]):
  1368. raise RuntimeError(
  1369. f"Duplicate evaluations made by {method}")
  1370. @pytest.mark.filterwarnings('ignore::RuntimeWarning')
  1371. @pytest.mark.parametrize('method', MINIMIZE_METHODS_NEW_CB)
  1372. @pytest.mark.parametrize('new_cb_interface', [0, 1, 2])
  1373. def test_callback_stopiteration(self, method, new_cb_interface):
  1374. # Check that if callback raises StopIteration, optimization
  1375. # terminates with the same result as if iterations were limited
  1376. def f(x):
  1377. f.flag = False # check that f isn't called after StopIteration
  1378. return optimize.rosen(x)
  1379. f.flag = False
  1380. def g(x):
  1381. f.flag = False
  1382. return optimize.rosen_der(x)
  1383. def h(x):
  1384. f.flag = False
  1385. return optimize.rosen_hess(x)
  1386. maxiter = 5
  1387. if new_cb_interface == 1:
  1388. def callback_interface(*, intermediate_result):
  1389. assert intermediate_result.fun == f(intermediate_result.x)
  1390. callback()
  1391. elif new_cb_interface == 2:
  1392. class Callback:
  1393. def __call__(self, intermediate_result: OptimizeResult):
  1394. assert intermediate_result.fun == f(intermediate_result.x)
  1395. callback()
  1396. callback_interface = Callback()
  1397. else:
  1398. def callback_interface(xk, *args): # type: ignore[misc]
  1399. callback()
  1400. def callback():
  1401. callback.i += 1
  1402. callback.flag = False
  1403. if callback.i == maxiter:
  1404. callback.flag = True
  1405. raise StopIteration()
  1406. callback.i = 0
  1407. callback.flag = False
  1408. kwargs = {'x0': [1.1]*5, 'method': method,
  1409. 'fun': f, 'jac': g, 'hess': h}
  1410. res = optimize.minimize(**kwargs, callback=callback_interface)
  1411. if method == 'nelder-mead':
  1412. maxiter = maxiter + 1 # nelder-mead counts differently
  1413. if method == 'cobyqa':
  1414. ref = optimize.minimize(**kwargs, options={'maxfev': maxiter})
  1415. assert res.nfev == ref.nfev == maxiter
  1416. elif method == 'cobyla':
  1417. # COBYLA calls the callback once per iteration, not once per function
  1418. # evaluation, so this test is not applicable. However we can test
  1419. # the COBYLA status to verify that res stopped back on the callback
  1420. # and ref stopped based on the iteration limit.
  1421. # COBYLA requires at least n+2 function evaluations
  1422. maxiter = max(maxiter, len(kwargs['x0'])+2)
  1423. ref = optimize.minimize(**kwargs, options={'maxiter': maxiter})
  1424. assert res.status == 30
  1425. assert res.message == ("Return from COBYLA because the callback function "
  1426. "requested termination")
  1427. assert ref.status == 3
  1428. assert ref.message == ("Return from COBYLA because the objective function "
  1429. "has been evaluated MAXFUN times.")
  1430. # Return early because res/ref will be unequal for COBYLA for the reasons
  1431. # mentioned above.
  1432. return
  1433. else:
  1434. ref = optimize.minimize(**kwargs, options={'maxiter': maxiter})
  1435. assert res.message.startswith("`callback` raised `StopIteration`")
  1436. assert res.nit == ref.nit == maxiter
  1437. if method != 'slsqp':
  1438. # Unlike all other methods, apparently SLSQP updates x/fun after the last
  1439. # call to the callback
  1440. assert res.fun == ref.fun
  1441. assert_equal(res.x, ref.x)
  1442. assert res.status == 3 if method in {'trust-constr', 'cobyqa'} else 99
  1443. if method != 'cobyqa':
  1444. assert not res.success
  1445. def test_ndim_error(self):
  1446. msg = "'x0' must only have one dimension."
  1447. with assert_raises(ValueError, match=msg):
  1448. optimize.minimize(lambda x: x, np.ones((2, 1)))
  1449. @pytest.mark.parametrize('method', ('nelder-mead', 'l-bfgs-b', 'tnc',
  1450. 'powell', 'cobyla', 'cobyqa',
  1451. 'trust-constr'))
  1452. def test_minimize_invalid_bounds(self, method):
  1453. def f(x):
  1454. return np.sum(x**2)
  1455. bounds = Bounds([1, 2], [3, 4])
  1456. msg = 'The number of bounds is not compatible with the length of `x0`.'
  1457. with pytest.raises(ValueError, match=msg):
  1458. optimize.minimize(f, x0=[1, 2, 3], method=method, bounds=bounds)
  1459. bounds = Bounds([1, 6, 1], [3, 4, 2])
  1460. msg = 'An upper bound is less than the corresponding lower bound.'
  1461. with pytest.raises(ValueError, match=msg):
  1462. optimize.minimize(f, x0=[1, 2, 3], method=method, bounds=bounds)
  1463. @pytest.mark.parametrize('method', ['bfgs', 'cg', 'newton-cg', 'powell'])
  1464. def test_minimize_warnings_gh1953(self, method):
  1465. # test that minimize methods produce warnings rather than just using
  1466. # `print`; see gh-1953.
  1467. kwargs = {} if method=='powell' else {'jac': optimize.rosen_der}
  1468. warning_type = (RuntimeWarning if method=='powell'
  1469. else optimize.OptimizeWarning)
  1470. options = {'disp': True, 'maxiter': 10}
  1471. with pytest.warns(warning_type, match='Maximum number'):
  1472. optimize.minimize(lambda x: optimize.rosen(x), [0, 0],
  1473. method=method, options=options, **kwargs)
  1474. options['disp'] = False
  1475. optimize.minimize(lambda x: optimize.rosen(x), [0, 0],
  1476. method=method, options=options, **kwargs)
  1477. @pytest.mark.parametrize(
  1478. 'method',
  1479. ['l-bfgs-b', 'tnc', 'Powell', 'Nelder-Mead', 'cobyqa']
  1480. )
  1481. def test_minimize_with_scalar(method):
  1482. # checks that minimize works with a scalar being provided to it.
  1483. def f(x):
  1484. return np.sum(x ** 2)
  1485. res = optimize.minimize(f, 17, bounds=[(-100, 100)], method=method)
  1486. assert res.success
  1487. assert_allclose(res.x, [0.0], atol=1e-5)
  1488. class TestLBFGSBBounds:
  1489. def setup_method(self):
  1490. self.bounds = ((1, None), (None, None))
  1491. self.solution = (1, 0)
  1492. def fun(self, x, p=2.0):
  1493. return 1.0 / p * (x[0]**p + x[1]**p)
  1494. def jac(self, x, p=2.0):
  1495. return x**(p - 1)
  1496. def fj(self, x, p=2.0):
  1497. return self.fun(x, p), self.jac(x, p)
  1498. def test_l_bfgs_b_bounds(self):
  1499. x, f, d = optimize.fmin_l_bfgs_b(self.fun, [0, -1],
  1500. fprime=self.jac,
  1501. bounds=self.bounds)
  1502. assert d['warnflag'] == 0, d['task']
  1503. assert_allclose(x, self.solution, atol=1e-6)
  1504. def test_l_bfgs_b_funjac(self):
  1505. # L-BFGS-B with fun and jac combined and extra arguments
  1506. x, f, d = optimize.fmin_l_bfgs_b(self.fj, [0, -1], args=(2.0, ),
  1507. bounds=self.bounds)
  1508. assert d['warnflag'] == 0, d['task']
  1509. assert_allclose(x, self.solution, atol=1e-6)
  1510. def test_minimize_l_bfgs_b_bounds(self):
  1511. # Minimize with method='L-BFGS-B' with bounds
  1512. res = optimize.minimize(self.fun, [0, -1], method='L-BFGS-B',
  1513. jac=self.jac, bounds=self.bounds)
  1514. assert res['success'], res['message']
  1515. assert_allclose(res.x, self.solution, atol=1e-6)
  1516. @pytest.mark.parametrize('bounds', [
  1517. ([(10, 1), (1, 10)]),
  1518. ([(1, 10), (10, 1)]),
  1519. ([(10, 1), (10, 1)])
  1520. ])
  1521. def test_minimize_l_bfgs_b_incorrect_bounds(self, bounds):
  1522. with pytest.raises(ValueError, match='.*bound.*'):
  1523. optimize.minimize(self.fun, [0, -1], method='L-BFGS-B',
  1524. jac=self.jac, bounds=bounds)
  1525. def test_minimize_l_bfgs_b_bounds_FD(self):
  1526. # test that initial starting value outside bounds doesn't raise
  1527. # an error (done with clipping).
  1528. # test all different finite differences combos, with and without args
  1529. jacs = ['2-point', '3-point', None]
  1530. argss = [(2.,), ()]
  1531. for jac, args in itertools.product(jacs, argss):
  1532. res = optimize.minimize(self.fun, [0, -1], args=args,
  1533. method='L-BFGS-B',
  1534. jac=jac, bounds=self.bounds,
  1535. options={'finite_diff_rel_step': None})
  1536. assert res['success'], res['message']
  1537. assert_allclose(res.x, self.solution, atol=1e-6)
  1538. class TestOptimizeScalar:
  1539. def setup_method(self):
  1540. self.solution = 1.5
  1541. def fun(self, x, a=1.5):
  1542. """Objective function"""
  1543. return (x - a)**2 - 0.8
  1544. def test_brent(self):
  1545. x = optimize.brent(self.fun)
  1546. assert_allclose(x, self.solution, atol=1e-6)
  1547. x = optimize.brent(self.fun, brack=(-3, -2))
  1548. assert_allclose(x, self.solution, atol=1e-6)
  1549. x = optimize.brent(self.fun, full_output=True)
  1550. assert_allclose(x[0], self.solution, atol=1e-6)
  1551. x = optimize.brent(self.fun, brack=(-15, -1, 15))
  1552. assert_allclose(x, self.solution, atol=1e-6)
  1553. message = r"\(f\(xb\) < f\(xa\)\) and \(f\(xb\) < f\(xc\)\)"
  1554. with pytest.raises(ValueError, match=message):
  1555. optimize.brent(self.fun, brack=(-1, 0, 1))
  1556. message = r"\(xa < xb\) and \(xb < xc\)"
  1557. with pytest.raises(ValueError, match=message):
  1558. optimize.brent(self.fun, brack=(0, -1, 1))
  1559. @pytest.mark.filterwarnings('ignore::UserWarning')
  1560. def test_golden(self):
  1561. x = optimize.golden(self.fun)
  1562. assert_allclose(x, self.solution, atol=1e-6)
  1563. x = optimize.golden(self.fun, brack=(-3, -2))
  1564. assert_allclose(x, self.solution, atol=1e-6)
  1565. x = optimize.golden(self.fun, full_output=True)
  1566. assert_allclose(x[0], self.solution, atol=1e-6)
  1567. x = optimize.golden(self.fun, brack=(-15, -1, 15))
  1568. assert_allclose(x, self.solution, atol=1e-6)
  1569. x = optimize.golden(self.fun, tol=0)
  1570. assert_allclose(x, self.solution)
  1571. maxiter_test_cases = [0, 1, 5]
  1572. for maxiter in maxiter_test_cases:
  1573. x0 = optimize.golden(self.fun, maxiter=0, full_output=True)
  1574. x = optimize.golden(self.fun, maxiter=maxiter, full_output=True)
  1575. nfev0, nfev = x0[2], x[2]
  1576. assert_equal(nfev - nfev0, maxiter)
  1577. message = r"\(f\(xb\) < f\(xa\)\) and \(f\(xb\) < f\(xc\)\)"
  1578. with pytest.raises(ValueError, match=message):
  1579. optimize.golden(self.fun, brack=(-1, 0, 1))
  1580. message = r"\(xa < xb\) and \(xb < xc\)"
  1581. with pytest.raises(ValueError, match=message):
  1582. optimize.golden(self.fun, brack=(0, -1, 1))
  1583. def test_fminbound(self):
  1584. x = optimize.fminbound(self.fun, 0, 1)
  1585. assert_allclose(x, 1, atol=1e-4)
  1586. x = optimize.fminbound(self.fun, 1, 5)
  1587. assert_allclose(x, self.solution, atol=1e-6)
  1588. x = optimize.fminbound(self.fun, np.array([1]), np.array([5]))
  1589. assert_allclose(x, self.solution, atol=1e-6)
  1590. assert_raises(ValueError, optimize.fminbound, self.fun, 5, 1)
  1591. def test_fminbound_scalar(self):
  1592. with pytest.raises(ValueError, match='.*must be finite scalars.*'):
  1593. optimize.fminbound(self.fun, np.zeros((1, 2)), 1)
  1594. x = optimize.fminbound(self.fun, 1, np.array(5))
  1595. assert_allclose(x, self.solution, atol=1e-6)
  1596. def test_gh11207(self):
  1597. def fun(x):
  1598. return x**2
  1599. optimize.fminbound(fun, 0, 0)
  1600. def test_minimize_scalar(self):
  1601. # combine all tests above for the minimize_scalar wrapper
  1602. x = optimize.minimize_scalar(self.fun).x
  1603. assert_allclose(x, self.solution, atol=1e-6)
  1604. x = optimize.minimize_scalar(self.fun, method='Brent')
  1605. assert x.success
  1606. x = optimize.minimize_scalar(self.fun, method='Brent',
  1607. options=dict(maxiter=3))
  1608. assert not x.success
  1609. x = optimize.minimize_scalar(self.fun, bracket=(-3, -2),
  1610. args=(1.5, ), method='Brent').x
  1611. assert_allclose(x, self.solution, atol=1e-6)
  1612. x = optimize.minimize_scalar(self.fun, method='Brent',
  1613. args=(1.5,)).x
  1614. assert_allclose(x, self.solution, atol=1e-6)
  1615. x = optimize.minimize_scalar(self.fun, bracket=(-15, -1, 15),
  1616. args=(1.5, ), method='Brent').x
  1617. assert_allclose(x, self.solution, atol=1e-6)
  1618. x = optimize.minimize_scalar(self.fun, bracket=(-3, -2),
  1619. args=(1.5, ), method='golden').x
  1620. assert_allclose(x, self.solution, atol=1e-6)
  1621. x = optimize.minimize_scalar(self.fun, method='golden',
  1622. args=(1.5,)).x
  1623. assert_allclose(x, self.solution, atol=1e-6)
  1624. x = optimize.minimize_scalar(self.fun, bracket=(-15, -1, 15),
  1625. args=(1.5, ), method='golden').x
  1626. assert_allclose(x, self.solution, atol=1e-6)
  1627. x = optimize.minimize_scalar(self.fun, bounds=(0, 1), args=(1.5,),
  1628. method='Bounded').x
  1629. assert_allclose(x, 1, atol=1e-4)
  1630. x = optimize.minimize_scalar(self.fun, bounds=(1, 5), args=(1.5, ),
  1631. method='bounded').x
  1632. assert_allclose(x, self.solution, atol=1e-6)
  1633. x = optimize.minimize_scalar(self.fun, bounds=(np.array([1]),
  1634. np.array([5])),
  1635. args=(np.array([1.5]), ),
  1636. method='bounded').x
  1637. assert_allclose(x, self.solution, atol=1e-6)
  1638. assert_raises(ValueError, optimize.minimize_scalar, self.fun,
  1639. bounds=(5, 1), method='bounded', args=(1.5, ))
  1640. assert_raises(ValueError, optimize.minimize_scalar, self.fun,
  1641. bounds=(np.zeros(2), 1), method='bounded', args=(1.5, ))
  1642. x = optimize.minimize_scalar(self.fun, bounds=(1, np.array(5)),
  1643. method='bounded').x
  1644. assert_allclose(x, self.solution, atol=1e-6)
  1645. def test_minimize_scalar_custom(self):
  1646. # This function comes from the documentation example.
  1647. def custmin(fun, bracket, args=(), maxfev=None, stepsize=0.1,
  1648. maxiter=100, callback=None, **options):
  1649. bestx = (bracket[1] + bracket[0]) / 2.0
  1650. besty = fun(bestx)
  1651. funcalls = 1
  1652. niter = 0
  1653. improved = True
  1654. stop = False
  1655. while improved and not stop and niter < maxiter:
  1656. improved = False
  1657. niter += 1
  1658. for testx in [bestx - stepsize, bestx + stepsize]:
  1659. testy = fun(testx, *args)
  1660. funcalls += 1
  1661. if testy < besty:
  1662. besty = testy
  1663. bestx = testx
  1664. improved = True
  1665. if callback is not None:
  1666. callback(bestx)
  1667. if maxfev is not None and funcalls >= maxfev:
  1668. stop = True
  1669. break
  1670. return optimize.OptimizeResult(fun=besty, x=bestx, nit=niter,
  1671. nfev=funcalls, success=(niter > 1))
  1672. res = optimize.minimize_scalar(self.fun, bracket=(0, 4),
  1673. method=custmin,
  1674. options=dict(stepsize=0.05))
  1675. assert_allclose(res.x, self.solution, atol=1e-6)
  1676. def test_minimize_scalar_coerce_args_param(self):
  1677. # Regression test for gh-3503
  1678. optimize.minimize_scalar(self.fun, args=1.5)
  1679. @pytest.mark.parametrize('method', ['brent', 'bounded', 'golden'])
  1680. def test_disp(self, method):
  1681. # test that all minimize_scalar methods accept a disp option.
  1682. for disp in [0, 1, 2, 3]:
  1683. optimize.minimize_scalar(self.fun, options={"disp": disp})
  1684. @pytest.mark.parametrize('method', ['brent', 'bounded', 'golden'])
  1685. def test_result_attributes(self, method):
  1686. kwargs = {"bounds": [-10, 10]} if method == 'bounded' else {}
  1687. result = optimize.minimize_scalar(self.fun, method=method, **kwargs)
  1688. assert hasattr(result, "x")
  1689. assert hasattr(result, "success")
  1690. assert hasattr(result, "message")
  1691. assert hasattr(result, "fun")
  1692. assert hasattr(result, "nfev")
  1693. assert hasattr(result, "nit")
  1694. @pytest.mark.filterwarnings('ignore::UserWarning')
  1695. @pytest.mark.parametrize('method', ['brent', 'bounded', 'golden'])
  1696. def test_nan_values(self, method):
  1697. # Check nan values result to failed exit status
  1698. count = [0]
  1699. def func(x):
  1700. count[0] += 1
  1701. if count[0] > 4:
  1702. return np.nan
  1703. else:
  1704. return x**2 + 0.1 * np.sin(x)
  1705. bracket = (-1, 0, 1)
  1706. bounds = (-1, 1)
  1707. with np.errstate(invalid='ignore'), warnings.catch_warnings():
  1708. warnings.filterwarnings("ignore", "delta_grad == 0.*", UserWarning)
  1709. warnings.filterwarnings(
  1710. "ignore", ".*does not use Hessian.*", RuntimeWarning)
  1711. warnings.filterwarnings(
  1712. "ignore", ".*does not use gradient.*", RuntimeWarning)
  1713. count = [0]
  1714. kwargs = {"bounds": bounds} if method == 'bounded' else {}
  1715. sol = optimize.minimize_scalar(func, bracket=bracket,
  1716. **kwargs, method=method,
  1717. options=dict(maxiter=20))
  1718. assert_equal(sol.success, False)
  1719. def test_minimize_scalar_defaults_gh10911(self):
  1720. # Previously, bounds were silently ignored unless `method='bounds'`
  1721. # was chosen. See gh-10911. Check that this is no longer the case.
  1722. def f(x):
  1723. return x**2
  1724. res = optimize.minimize_scalar(f)
  1725. assert_allclose(res.x, 0, atol=1e-8)
  1726. res = optimize.minimize_scalar(f, bounds=(1, 100),
  1727. options={'xatol': 1e-10})
  1728. assert_allclose(res.x, 1)
  1729. def test_minimize_non_finite_bounds_gh10911(self):
  1730. # Previously, minimize_scalar misbehaved with infinite bounds.
  1731. # See gh-10911. Check that it now raises an error, instead.
  1732. msg = "Optimization bounds must be finite scalars."
  1733. with pytest.raises(ValueError, match=msg):
  1734. optimize.minimize_scalar(np.sin, bounds=(1, np.inf))
  1735. with pytest.raises(ValueError, match=msg):
  1736. optimize.minimize_scalar(np.sin, bounds=(np.nan, 1))
  1737. @pytest.mark.parametrize("method", ['brent', 'golden'])
  1738. def test_minimize_unbounded_method_with_bounds_gh10911(self, method):
  1739. # Previously, `bounds` were silently ignored when `method='brent'` or
  1740. # `method='golden'`. See gh-10911. Check that error is now raised.
  1741. msg = "Use of `bounds` is incompatible with..."
  1742. with pytest.raises(ValueError, match=msg):
  1743. optimize.minimize_scalar(np.sin, method=method, bounds=(1, 2))
  1744. @pytest.mark.filterwarnings('ignore::RuntimeWarning')
  1745. @pytest.mark.parametrize("method", MINIMIZE_SCALAR_METHODS)
  1746. @pytest.mark.parametrize("tol", [1, 1e-6])
  1747. @pytest.mark.parametrize("fshape", [(), (1,), (1, 1)])
  1748. def test_minimize_scalar_dimensionality_gh16196(self, method, tol, fshape):
  1749. # gh-16196 reported that the output shape of `minimize_scalar` was not
  1750. # consistent when an objective function returned an array. Check that
  1751. # `res.fun` and `res.x` are now consistent.
  1752. def f(x):
  1753. return np.array(x**4).reshape(fshape)
  1754. a, b = -0.1, 0.2
  1755. kwargs = (dict(bracket=(a, b)) if method != "bounded"
  1756. else dict(bounds=(a, b)))
  1757. kwargs.update(dict(method=method, tol=tol))
  1758. res = optimize.minimize_scalar(f, **kwargs)
  1759. assert res.x.shape == res.fun.shape == f(res.x).shape == fshape
  1760. @pytest.mark.parametrize('method', ['bounded', 'brent', 'golden'])
  1761. def test_minimize_scalar_warnings_gh1953(self, method):
  1762. # test that minimize_scalar methods produce warnings rather than just
  1763. # using `print`; see gh-1953.
  1764. def f(x):
  1765. return (x - 1)**2
  1766. kwargs = {}
  1767. kwd = 'bounds' if method == 'bounded' else 'bracket'
  1768. kwargs[kwd] = [-2, 10]
  1769. options = {'disp': True, 'maxiter': 3}
  1770. with pytest.warns(optimize.OptimizeWarning, match='Maximum number'):
  1771. optimize.minimize_scalar(f, method=method, options=options,
  1772. **kwargs)
  1773. options['disp'] = False
  1774. optimize.minimize_scalar(f, method=method, options=options, **kwargs)
  1775. class TestBracket:
  1776. @pytest.mark.filterwarnings('ignore::RuntimeWarning')
  1777. def test_errors_and_status_false(self):
  1778. # Check that `bracket` raises the errors it is supposed to
  1779. def f(x): # gh-14858
  1780. return x**2 if ((-1 < x) & (x < 1)) else 100.0
  1781. message = "The algorithm terminated without finding a valid bracket."
  1782. with pytest.raises(RuntimeError, match=message):
  1783. optimize.bracket(f, -1, 1)
  1784. with pytest.raises(RuntimeError, match=message):
  1785. optimize.bracket(f, -1, np.inf)
  1786. with pytest.raises(RuntimeError, match=message):
  1787. optimize.brent(f, brack=(-1, 1))
  1788. with pytest.raises(RuntimeError, match=message):
  1789. optimize.golden(f, brack=(-1, 1))
  1790. def f(x): # gh-5899
  1791. return -5 * x**5 + 4 * x**4 - 12 * x**3 + 11 * x**2 - 2 * x + 1
  1792. message = "No valid bracket was found before the iteration limit..."
  1793. with pytest.raises(RuntimeError, match=message):
  1794. optimize.bracket(f, -0.5, 0.5, maxiter=10)
  1795. @pytest.mark.parametrize('method', ('brent', 'golden'))
  1796. def test_minimize_scalar_success_false(self, method):
  1797. # Check that status information from `bracket` gets to minimize_scalar
  1798. def f(x): # gh-14858
  1799. return x**2 if ((-1 < x) & (x < 1)) else 100.0
  1800. message = "The algorithm terminated without finding a valid bracket."
  1801. res = optimize.minimize_scalar(f, bracket=(-1, 1), method=method)
  1802. assert not res.success
  1803. assert message in res.message
  1804. assert res.nfev == 3
  1805. assert res.nit == 0
  1806. assert res.fun == 100
  1807. def test_brent_negative_tolerance():
  1808. assert_raises(ValueError, optimize.brent, np.cos, tol=-.01)
  1809. class TestNewtonCg:
  1810. def test_rosenbrock(self):
  1811. x0 = np.array([-1.2, 1.0])
  1812. sol = optimize.minimize(optimize.rosen, x0,
  1813. jac=optimize.rosen_der,
  1814. hess=optimize.rosen_hess,
  1815. tol=1e-5,
  1816. method='Newton-CG')
  1817. assert sol.success, sol.message
  1818. assert_allclose(sol.x, np.array([1, 1]), rtol=1e-4)
  1819. def test_himmelblau(self):
  1820. x0 = np.array(himmelblau_x0)
  1821. sol = optimize.minimize(himmelblau,
  1822. x0,
  1823. jac=himmelblau_grad,
  1824. hess=himmelblau_hess,
  1825. method='Newton-CG',
  1826. tol=1e-6)
  1827. assert sol.success, sol.message
  1828. assert_allclose(sol.x, himmelblau_xopt, rtol=1e-4)
  1829. assert_allclose(sol.fun, himmelblau_min, atol=1e-4)
  1830. def test_finite_difference(self):
  1831. x0 = np.array([-1.2, 1.0])
  1832. sol = optimize.minimize(optimize.rosen, x0,
  1833. jac=optimize.rosen_der,
  1834. hess='2-point',
  1835. tol=1e-5,
  1836. method='Newton-CG')
  1837. assert sol.success, sol.message
  1838. assert_allclose(sol.x, np.array([1, 1]), rtol=1e-4)
  1839. def test_hessian_update_strategy(self):
  1840. x0 = np.array([-1.2, 1.0])
  1841. sol = optimize.minimize(optimize.rosen, x0,
  1842. jac=optimize.rosen_der,
  1843. hess=optimize.BFGS(),
  1844. tol=1e-5,
  1845. method='Newton-CG')
  1846. assert sol.success, sol.message
  1847. assert_allclose(sol.x, np.array([1, 1]), rtol=1e-4)
  1848. def test_line_for_search():
  1849. # _line_for_search is only used in _linesearch_powell, which is also
  1850. # tested below. Thus there are more tests of _line_for_search in the
  1851. # test_linesearch_powell_bounded function.
  1852. line_for_search = optimize._optimize._line_for_search
  1853. # args are x0, alpha, lower_bound, upper_bound
  1854. # returns lmin, lmax
  1855. lower_bound = np.array([-5.3, -1, -1.5, -3])
  1856. upper_bound = np.array([1.9, 1, 2.8, 3])
  1857. # test when starting in the bounds
  1858. x0 = np.array([0., 0, 0, 0])
  1859. # and when starting outside of the bounds
  1860. x1 = np.array([0., 2, -3, 0])
  1861. all_tests = (
  1862. (x0, np.array([1., 0, 0, 0]), -5.3, 1.9),
  1863. (x0, np.array([0., 1, 0, 0]), -1, 1),
  1864. (x0, np.array([0., 0, 1, 0]), -1.5, 2.8),
  1865. (x0, np.array([0., 0, 0, 1]), -3, 3),
  1866. (x0, np.array([1., 1, 0, 0]), -1, 1),
  1867. (x0, np.array([1., 0, -1, 2]), -1.5, 1.5),
  1868. (x0, np.array([2., 0, -1, 2]), -1.5, 0.95),
  1869. (x1, np.array([1., 0, 0, 0]), -5.3, 1.9),
  1870. (x1, np.array([0., 1, 0, 0]), -3, -1),
  1871. (x1, np.array([0., 0, 1, 0]), 1.5, 5.8),
  1872. (x1, np.array([0., 0, 0, 1]), -3, 3),
  1873. (x1, np.array([1., 1, 0, 0]), -3, -1),
  1874. (x1, np.array([1., 0, -1, 0]), -5.3, -1.5),
  1875. )
  1876. for x, alpha, lmin, lmax in all_tests:
  1877. mi, ma = line_for_search(x, alpha, lower_bound, upper_bound)
  1878. assert_allclose(mi, lmin, atol=1e-6)
  1879. assert_allclose(ma, lmax, atol=1e-6)
  1880. # now with infinite bounds
  1881. lower_bound = np.array([-np.inf, -1, -np.inf, -3])
  1882. upper_bound = np.array([np.inf, 1, 2.8, np.inf])
  1883. all_tests = (
  1884. (x0, np.array([1., 0, 0, 0]), -np.inf, np.inf),
  1885. (x0, np.array([0., 1, 0, 0]), -1, 1),
  1886. (x0, np.array([0., 0, 1, 0]), -np.inf, 2.8),
  1887. (x0, np.array([0., 0, 0, 1]), -3, np.inf),
  1888. (x0, np.array([1., 1, 0, 0]), -1, 1),
  1889. (x0, np.array([1., 0, -1, 2]), -1.5, np.inf),
  1890. (x1, np.array([1., 0, 0, 0]), -np.inf, np.inf),
  1891. (x1, np.array([0., 1, 0, 0]), -3, -1),
  1892. (x1, np.array([0., 0, 1, 0]), -np.inf, 5.8),
  1893. (x1, np.array([0., 0, 0, 1]), -3, np.inf),
  1894. (x1, np.array([1., 1, 0, 0]), -3, -1),
  1895. (x1, np.array([1., 0, -1, 0]), -5.8, np.inf),
  1896. )
  1897. for x, alpha, lmin, lmax in all_tests:
  1898. mi, ma = line_for_search(x, alpha, lower_bound, upper_bound)
  1899. assert_allclose(mi, lmin, atol=1e-6)
  1900. assert_allclose(ma, lmax, atol=1e-6)
  1901. def test_linesearch_powell():
  1902. # helper function in optimize.py, not a public function.
  1903. linesearch_powell = optimize._optimize._linesearch_powell
  1904. # args are func, p, xi, fval, lower_bound=None, upper_bound=None, tol=1e-3
  1905. # returns new_fval, p + direction, direction
  1906. def func(x):
  1907. return np.sum((x - np.array([-1.0, 2.0, 1.5, -0.4])) ** 2)
  1908. p0 = np.array([0., 0, 0, 0])
  1909. fval = func(p0)
  1910. lower_bound = np.array([-np.inf] * 4)
  1911. upper_bound = np.array([np.inf] * 4)
  1912. all_tests = (
  1913. (np.array([1., 0, 0, 0]), -1),
  1914. (np.array([0., 1, 0, 0]), 2),
  1915. (np.array([0., 0, 1, 0]), 1.5),
  1916. (np.array([0., 0, 0, 1]), -.4),
  1917. (np.array([-1., 0, 1, 0]), 1.25),
  1918. (np.array([0., 0, 1, 1]), .55),
  1919. (np.array([2., 0, -1, 1]), -.65),
  1920. )
  1921. for xi, l in all_tests:
  1922. f, p, direction = linesearch_powell(func, p0, xi,
  1923. fval=fval, tol=1e-5)
  1924. assert_allclose(f, func(l * xi), atol=1e-6)
  1925. assert_allclose(p, l * xi, atol=1e-6)
  1926. assert_allclose(direction, l * xi, atol=1e-6)
  1927. f, p, direction = linesearch_powell(func, p0, xi, tol=1e-5,
  1928. lower_bound=lower_bound,
  1929. upper_bound=upper_bound,
  1930. fval=fval)
  1931. assert_allclose(f, func(l * xi), atol=1e-6)
  1932. assert_allclose(p, l * xi, atol=1e-6)
  1933. assert_allclose(direction, l * xi, atol=1e-6)
  1934. def test_linesearch_powell_bounded():
  1935. # helper function in optimize.py, not a public function.
  1936. linesearch_powell = optimize._optimize._linesearch_powell
  1937. # args are func, p, xi, fval, lower_bound=None, upper_bound=None, tol=1e-3
  1938. # returns new_fval, p+direction, direction
  1939. def func(x):
  1940. return np.sum((x - np.array([-1.0, 2.0, 1.5, -0.4])) ** 2)
  1941. p0 = np.array([0., 0, 0, 0])
  1942. fval = func(p0)
  1943. # first choose bounds such that the same tests from
  1944. # test_linesearch_powell should pass.
  1945. lower_bound = np.array([-2.]*4)
  1946. upper_bound = np.array([2.]*4)
  1947. all_tests = (
  1948. (np.array([1., 0, 0, 0]), -1),
  1949. (np.array([0., 1, 0, 0]), 2),
  1950. (np.array([0., 0, 1, 0]), 1.5),
  1951. (np.array([0., 0, 0, 1]), -.4),
  1952. (np.array([-1., 0, 1, 0]), 1.25),
  1953. (np.array([0., 0, 1, 1]), .55),
  1954. (np.array([2., 0, -1, 1]), -.65),
  1955. )
  1956. for xi, l in all_tests:
  1957. f, p, direction = linesearch_powell(func, p0, xi, tol=1e-5,
  1958. lower_bound=lower_bound,
  1959. upper_bound=upper_bound,
  1960. fval=fval)
  1961. assert_allclose(f, func(l * xi), atol=1e-6)
  1962. assert_allclose(p, l * xi, atol=1e-6)
  1963. assert_allclose(direction, l * xi, atol=1e-6)
  1964. # now choose bounds such that unbounded vs bounded gives different results
  1965. lower_bound = np.array([-.3]*3 + [-1])
  1966. upper_bound = np.array([.45]*3 + [.9])
  1967. all_tests = (
  1968. (np.array([1., 0, 0, 0]), -.3),
  1969. (np.array([0., 1, 0, 0]), .45),
  1970. (np.array([0., 0, 1, 0]), .45),
  1971. (np.array([0., 0, 0, 1]), -.4),
  1972. (np.array([-1., 0, 1, 0]), .3),
  1973. (np.array([0., 0, 1, 1]), .45),
  1974. (np.array([2., 0, -1, 1]), -.15),
  1975. )
  1976. for xi, l in all_tests:
  1977. f, p, direction = linesearch_powell(func, p0, xi, tol=1e-5,
  1978. lower_bound=lower_bound,
  1979. upper_bound=upper_bound,
  1980. fval=fval)
  1981. assert_allclose(f, func(l * xi), atol=1e-6)
  1982. assert_allclose(p, l * xi, atol=1e-6)
  1983. assert_allclose(direction, l * xi, atol=1e-6)
  1984. # now choose as above but start outside the bounds
  1985. p0 = np.array([-1., 0, 0, 2])
  1986. fval = func(p0)
  1987. all_tests = (
  1988. (np.array([1., 0, 0, 0]), .7),
  1989. (np.array([0., 1, 0, 0]), .45),
  1990. (np.array([0., 0, 1, 0]), .45),
  1991. (np.array([0., 0, 0, 1]), -2.4),
  1992. )
  1993. for xi, l in all_tests:
  1994. f, p, direction = linesearch_powell(func, p0, xi, tol=1e-5,
  1995. lower_bound=lower_bound,
  1996. upper_bound=upper_bound,
  1997. fval=fval)
  1998. assert_allclose(f, func(p0 + l * xi), atol=1e-6)
  1999. assert_allclose(p, p0 + l * xi, atol=1e-6)
  2000. assert_allclose(direction, l * xi, atol=1e-6)
  2001. # now mix in inf
  2002. p0 = np.array([0., 0, 0, 0])
  2003. fval = func(p0)
  2004. # now choose bounds that mix inf
  2005. lower_bound = np.array([-.3, -np.inf, -np.inf, -1])
  2006. upper_bound = np.array([np.inf, .45, np.inf, .9])
  2007. all_tests = (
  2008. (np.array([1., 0, 0, 0]), -.3),
  2009. (np.array([0., 1, 0, 0]), .45),
  2010. (np.array([0., 0, 1, 0]), 1.5),
  2011. (np.array([0., 0, 0, 1]), -.4),
  2012. (np.array([-1., 0, 1, 0]), .3),
  2013. (np.array([0., 0, 1, 1]), .55),
  2014. (np.array([2., 0, -1, 1]), -.15),
  2015. )
  2016. for xi, l in all_tests:
  2017. f, p, direction = linesearch_powell(func, p0, xi, tol=1e-5,
  2018. lower_bound=lower_bound,
  2019. upper_bound=upper_bound,
  2020. fval=fval)
  2021. assert_allclose(f, func(l * xi), atol=1e-6)
  2022. assert_allclose(p, l * xi, atol=1e-6)
  2023. assert_allclose(direction, l * xi, atol=1e-6)
  2024. # now choose as above but start outside the bounds
  2025. p0 = np.array([-1., 0, 0, 2])
  2026. fval = func(p0)
  2027. all_tests = (
  2028. (np.array([1., 0, 0, 0]), .7),
  2029. (np.array([0., 1, 0, 0]), .45),
  2030. (np.array([0., 0, 1, 0]), 1.5),
  2031. (np.array([0., 0, 0, 1]), -2.4),
  2032. )
  2033. for xi, l in all_tests:
  2034. f, p, direction = linesearch_powell(func, p0, xi, tol=1e-5,
  2035. lower_bound=lower_bound,
  2036. upper_bound=upper_bound,
  2037. fval=fval)
  2038. assert_allclose(f, func(p0 + l * xi), atol=1e-6)
  2039. assert_allclose(p, p0 + l * xi, atol=1e-6)
  2040. assert_allclose(direction, l * xi, atol=1e-6)
  2041. def test_powell_limits():
  2042. # gh15342 - powell was going outside bounds for some function evaluations.
  2043. bounds = optimize.Bounds([0, 0], [0.6, 20])
  2044. def fun(x):
  2045. a, b = x
  2046. assert (x >= bounds.lb).all() and (x <= bounds.ub).all()
  2047. return a ** 2 + b ** 2
  2048. optimize.minimize(fun, x0=[0.6, 20], method='Powell', bounds=bounds)
  2049. # Another test from the original report - gh-13411
  2050. bounds = optimize.Bounds(lb=[0,], ub=[1,], keep_feasible=[True,])
  2051. def func(x):
  2052. assert x >= 0 and x <= 1
  2053. return np.exp(x)
  2054. optimize.minimize(fun=func, x0=[0.5], method='powell', bounds=bounds)
  2055. def test_powell_output():
  2056. funs = [rosen, lambda x: np.array(rosen(x)), lambda x: np.array([rosen(x)])]
  2057. for fun in funs:
  2058. res = optimize.minimize(fun, x0=[0.6, 20], method='Powell')
  2059. assert np.isscalar(res.fun)
  2060. class TestRosen:
  2061. @make_xp_test_case(optimize.rosen)
  2062. def test_rosen(self, xp):
  2063. # integer input should be promoted to the default floating type
  2064. x = xp.asarray([1, 1, 1])
  2065. xp_assert_equal(optimize.rosen(x),
  2066. xp.asarray(0.))
  2067. @make_xp_test_case(optimize.rosen_der)
  2068. def test_rosen_der(self, xp):
  2069. x = xp.asarray([1, 1, 1, 1])
  2070. xp_assert_equal(optimize.rosen_der(x),
  2071. xp.zeros_like(x, dtype=xp.asarray(1.).dtype))
  2072. @make_xp_test_case(optimize.rosen_hess, optimize.rosen_hess_prod)
  2073. def test_hess_prod(self, xp):
  2074. one = xp.asarray(1.)
  2075. # Compare rosen_hess(x) times p with rosen_hess_prod(x,p). See gh-1775.
  2076. x = xp.asarray([3, 4, 5])
  2077. p = xp.asarray([2, 2, 2])
  2078. hp = optimize.rosen_hess_prod(x, p)
  2079. p = xp.astype(p, one.dtype)
  2080. dothp = optimize.rosen_hess(x) @ p
  2081. xp_assert_equal(hp, dothp)
  2082. def himmelblau(p):
  2083. """
  2084. R^2 -> R^1 test function for optimization. The function has four local
  2085. minima where himmelblau(xopt) == 0.
  2086. """
  2087. x, y = p
  2088. a = x*x + y - 11
  2089. b = x + y*y - 7
  2090. return a*a + b*b
  2091. def himmelblau_grad(p):
  2092. x, y = p
  2093. return np.array([4*x**3 + 4*x*y - 42*x + 2*y**2 - 14,
  2094. 2*x**2 + 4*x*y + 4*y**3 - 26*y - 22])
  2095. def himmelblau_hess(p):
  2096. x, y = p
  2097. return np.array([[12*x**2 + 4*y - 42, 4*x + 4*y],
  2098. [4*x + 4*y, 4*x + 12*y**2 - 26]])
  2099. himmelblau_x0 = [-0.27, -0.9]
  2100. himmelblau_xopt = [3, 2]
  2101. himmelblau_min = 0.0
  2102. def test_minimize_multiple_constraints():
  2103. # Regression test for gh-4240.
  2104. def func(x):
  2105. return np.array([25 - 0.2 * x[0] - 0.4 * x[1] - 0.33 * x[2]])
  2106. def func1(x):
  2107. return np.array([x[1]])
  2108. def func2(x):
  2109. return np.array([x[2]])
  2110. cons = ({'type': 'ineq', 'fun': func},
  2111. {'type': 'ineq', 'fun': func1},
  2112. {'type': 'ineq', 'fun': func2})
  2113. def f(x):
  2114. return -1 * (x[0] + x[1] + x[2])
  2115. res = optimize.minimize(f, [0, 0, 0], method='SLSQP', constraints=cons)
  2116. assert_allclose(res.x, [125, 0, 0], atol=1e-10)
  2117. class TestOptimizeResultAttributes:
  2118. # Test that all minimizers return an OptimizeResult containing
  2119. # all the OptimizeResult attributes
  2120. def setup_method(self):
  2121. self.x0 = [5, 5]
  2122. self.func = optimize.rosen
  2123. self.jac = optimize.rosen_der
  2124. self.hess = optimize.rosen_hess
  2125. self.hessp = optimize.rosen_hess_prod
  2126. self.bounds = [(0., 10.), (0., 10.)]
  2127. @pytest.mark.fail_slow(2)
  2128. def test_attributes_present(self):
  2129. attributes = ['nit', 'nfev', 'x', 'success', 'status', 'fun',
  2130. 'message']
  2131. skip = {'cobyla': ['nit']}
  2132. for method in MINIMIZE_METHODS:
  2133. with warnings.catch_warnings():
  2134. warnings.filterwarnings("ignore",
  2135. ("Method .+ does not use (gradient|Hessian.*)"
  2136. " information"), RuntimeWarning)
  2137. res = optimize.minimize(self.func, self.x0, method=method,
  2138. jac=self.jac, hess=self.hess,
  2139. hessp=self.hessp)
  2140. for attribute in attributes:
  2141. if method in skip and attribute in skip[method]:
  2142. continue
  2143. assert hasattr(res, attribute)
  2144. assert attribute in dir(res)
  2145. # gh13001, OptimizeResult.message should be a str
  2146. assert isinstance(res.message, str)
  2147. def f1(z, *params):
  2148. x, y = z
  2149. a, b, c, d, e, f, g, h, i, j, k, l, scale = params
  2150. return (a * x**2 + b * x * y + c * y**2 + d*x + e*y + f)
  2151. def f2(z, *params):
  2152. x, y = z
  2153. a, b, c, d, e, f, g, h, i, j, k, l, scale = params
  2154. return (-g*np.exp(-((x-h)**2 + (y-i)**2) / scale))
  2155. def f3(z, *params):
  2156. x, y = z
  2157. a, b, c, d, e, f, g, h, i, j, k, l, scale = params
  2158. return (-j*np.exp(-((x-k)**2 + (y-l)**2) / scale))
  2159. def brute_func(z, *params):
  2160. return f1(z, *params) + f2(z, *params) + f3(z, *params)
  2161. class TestBrute:
  2162. # Test the "brute force" method
  2163. def setup_method(self):
  2164. self.params = (2, 3, 7, 8, 9, 10, 44, -1, 2, 26, 1, -2, 0.5)
  2165. self.rranges = (slice(-4, 4, 0.25), slice(-4, 4, 0.25))
  2166. self.solution = np.array([-1.05665192, 1.80834843])
  2167. def brute_func(self, z, *params):
  2168. # an instance method optimizing
  2169. return brute_func(z, *params)
  2170. def test_brute(self):
  2171. # test fmin
  2172. resbrute = optimize.brute(brute_func, self.rranges, args=self.params,
  2173. full_output=True, finish=optimize.fmin)
  2174. assert_allclose(resbrute[0], self.solution, atol=1e-3)
  2175. assert_allclose(resbrute[1], brute_func(self.solution, *self.params),
  2176. atol=1e-3)
  2177. # test minimize
  2178. resbrute = optimize.brute(brute_func, self.rranges, args=self.params,
  2179. full_output=True,
  2180. finish=optimize.minimize)
  2181. assert_allclose(resbrute[0], self.solution, atol=1e-3)
  2182. assert_allclose(resbrute[1], brute_func(self.solution, *self.params),
  2183. atol=1e-3)
  2184. # test that brute can optimize an instance method (the other tests use
  2185. # a non-class based function
  2186. resbrute = optimize.brute(self.brute_func, self.rranges,
  2187. args=self.params, full_output=True,
  2188. finish=optimize.minimize)
  2189. assert_allclose(resbrute[0], self.solution, atol=1e-3)
  2190. def test_1D(self):
  2191. # test that for a 1-D problem the test function is passed an array,
  2192. # not a scalar.
  2193. def f(x):
  2194. assert len(x.shape) == 1
  2195. assert x.shape[0] == 1
  2196. return x ** 2
  2197. optimize.brute(f, [(-1, 1)], Ns=3, finish=None)
  2198. @pytest.mark.fail_slow(10)
  2199. def test_workers(self):
  2200. # check that parallel evaluation works
  2201. resbrute = optimize.brute(brute_func, self.rranges, args=self.params,
  2202. full_output=True, finish=None)
  2203. resbrute1 = optimize.brute(brute_func, self.rranges, args=self.params,
  2204. full_output=True, finish=None, workers=2)
  2205. assert_allclose(resbrute1[-1], resbrute[-1])
  2206. assert_allclose(resbrute1[0], resbrute[0])
  2207. def test_runtime_warning(self, capsys):
  2208. rng = np.random.default_rng(1234)
  2209. def func(z, *params):
  2210. return rng.random(1) * 1000 # never converged problem
  2211. msg = "final optimization did not succeed.*|Maximum number of function eval.*"
  2212. with pytest.warns(RuntimeWarning, match=msg):
  2213. optimize.brute(func, self.rranges, args=self.params, disp=True)
  2214. def test_coerce_args_param(self):
  2215. # optimize.brute should coerce non-iterable args to a tuple.
  2216. def f(x, *args):
  2217. return x ** args[0]
  2218. resbrute = optimize.brute(f, (slice(-4, 4, .25),), args=2)
  2219. assert_allclose(resbrute, 0)
  2220. @pytest.mark.fail_slow(20)
  2221. def test_cobyla_threadsafe():
  2222. # Verify that cobyla is threadsafe. Will segfault if it is not.
  2223. import concurrent.futures
  2224. import time
  2225. def objective1(x):
  2226. time.sleep(0.1)
  2227. return x[0]**2
  2228. def objective2(x):
  2229. time.sleep(0.1)
  2230. return (x[0]-1)**2
  2231. min_method = "COBYLA"
  2232. def minimizer1():
  2233. return optimize.minimize(objective1,
  2234. [0.0],
  2235. method=min_method)
  2236. def minimizer2():
  2237. return optimize.minimize(objective2,
  2238. [0.0],
  2239. method=min_method)
  2240. with concurrent.futures.ThreadPoolExecutor() as pool:
  2241. tasks = []
  2242. tasks.append(pool.submit(minimizer1))
  2243. tasks.append(pool.submit(minimizer2))
  2244. for t in tasks:
  2245. t.result()
  2246. class TestIterationLimits:
  2247. # Tests that optimisation does not give up before trying requested
  2248. # number of iterations or evaluations. And that it does not succeed
  2249. # by exceeding the limits.
  2250. def setup_method(self):
  2251. self.funcalls = threading.local()
  2252. def slow_func(self, v):
  2253. if not hasattr(self.funcalls, 'c'):
  2254. self.funcalls.c = 0
  2255. self.funcalls.c += 1
  2256. r, t = np.sqrt(v[0]**2+v[1]**2), np.arctan2(v[0], v[1])
  2257. return np.sin(r*20 + t)+r*0.5
  2258. @pytest.mark.fail_slow(10)
  2259. def test_neldermead_limit(self):
  2260. self.check_limits("Nelder-Mead", 200)
  2261. def test_powell_limit(self):
  2262. self.check_limits("powell", 1000)
  2263. def check_limits(self, method, default_iters):
  2264. for start_v in [[0.1, 0.1], [1, 1], [2, 2]]:
  2265. for mfev in [50, 500, 5000]:
  2266. self.funcalls.c = 0
  2267. res = optimize.minimize(self.slow_func, start_v,
  2268. method=method,
  2269. options={"maxfev": mfev})
  2270. assert self.funcalls.c == res["nfev"]
  2271. if res["success"]:
  2272. assert res["nfev"] < mfev
  2273. else:
  2274. assert res["nfev"] >= mfev
  2275. for mit in [50, 500, 5000]:
  2276. res = optimize.minimize(self.slow_func, start_v,
  2277. method=method,
  2278. options={"maxiter": mit})
  2279. if res["success"]:
  2280. assert res["nit"] <= mit
  2281. else:
  2282. assert res["nit"] >= mit
  2283. for mfev, mit in [[50, 50], [5000, 5000], [5000, np.inf]]:
  2284. self.funcalls.c = 0
  2285. res = optimize.minimize(self.slow_func, start_v,
  2286. method=method,
  2287. options={"maxiter": mit,
  2288. "maxfev": mfev})
  2289. assert self.funcalls.c == res["nfev"]
  2290. if res["success"]:
  2291. assert res["nfev"] < mfev and res["nit"] <= mit
  2292. else:
  2293. assert res["nfev"] >= mfev or res["nit"] >= mit
  2294. for mfev, mit in [[np.inf, None], [None, np.inf]]:
  2295. self.funcalls.c = 0
  2296. res = optimize.minimize(self.slow_func, start_v,
  2297. method=method,
  2298. options={"maxiter": mit,
  2299. "maxfev": mfev})
  2300. assert self.funcalls.c == res["nfev"]
  2301. if res["success"]:
  2302. if mfev is None:
  2303. assert res["nfev"] < default_iters*2
  2304. else:
  2305. assert res["nit"] <= default_iters*2
  2306. else:
  2307. assert (res["nfev"] >= default_iters*2
  2308. or res["nit"] >= default_iters*2)
  2309. def test_result_x_shape_when_len_x_is_one():
  2310. def fun(x):
  2311. return x * x
  2312. def jac(x):
  2313. return 2. * x
  2314. def hess(x):
  2315. return np.array([[2.]])
  2316. methods = ['Nelder-Mead', 'Powell', 'CG', 'BFGS', 'L-BFGS-B', 'TNC',
  2317. 'COBYLA', 'COBYQA', 'SLSQP']
  2318. for method in methods:
  2319. res = optimize.minimize(fun, np.array([0.1]), method=method)
  2320. assert res.x.shape == (1,)
  2321. # use jac + hess
  2322. methods = ['trust-constr', 'dogleg', 'trust-ncg', 'trust-exact',
  2323. 'trust-krylov', 'Newton-CG']
  2324. for method in methods:
  2325. res = optimize.minimize(fun, np.array([0.1]), method=method, jac=jac,
  2326. hess=hess)
  2327. assert res.x.shape == (1,)
  2328. class FunctionWithGradient:
  2329. def __init__(self):
  2330. self.number_of_calls = threading.local()
  2331. def __call__(self, x):
  2332. if not hasattr(self.number_of_calls, 'c'):
  2333. self.number_of_calls.c = 0
  2334. self.number_of_calls.c += 1
  2335. return np.sum(x**2), 2 * x
  2336. @pytest.fixture
  2337. def function_with_gradient():
  2338. return FunctionWithGradient()
  2339. def test_memoize_jac_function_before_gradient(function_with_gradient):
  2340. memoized_function = MemoizeJac(function_with_gradient)
  2341. x0 = np.array([1.0, 2.0])
  2342. assert_allclose(memoized_function(x0), 5.0)
  2343. assert function_with_gradient.number_of_calls.c == 1
  2344. assert_allclose(memoized_function.derivative(x0), 2 * x0)
  2345. assert function_with_gradient.number_of_calls.c == 1, \
  2346. "function is not recomputed " \
  2347. "if gradient is requested after function value"
  2348. assert_allclose(
  2349. memoized_function(2 * x0), 20.0,
  2350. err_msg="different input triggers new computation")
  2351. assert function_with_gradient.number_of_calls.c == 2, \
  2352. "different input triggers new computation"
  2353. def test_memoize_jac_gradient_before_function(function_with_gradient):
  2354. memoized_function = MemoizeJac(function_with_gradient)
  2355. x0 = np.array([1.0, 2.0])
  2356. assert_allclose(memoized_function.derivative(x0), 2 * x0)
  2357. assert function_with_gradient.number_of_calls.c == 1
  2358. assert_allclose(memoized_function(x0), 5.0)
  2359. assert function_with_gradient.number_of_calls.c == 1, \
  2360. "function is not recomputed " \
  2361. "if function value is requested after gradient"
  2362. assert_allclose(
  2363. memoized_function.derivative(2 * x0), 4 * x0,
  2364. err_msg="different input triggers new computation")
  2365. assert function_with_gradient.number_of_calls.c == 2, \
  2366. "different input triggers new computation"
  2367. def test_memoize_jac_with_bfgs(function_with_gradient):
  2368. """ Tests that using MemoizedJac in combination with ScalarFunction
  2369. and BFGS does not lead to repeated function evaluations.
  2370. Tests changes made in response to GH11868.
  2371. """
  2372. memoized_function = MemoizeJac(function_with_gradient)
  2373. jac = memoized_function.derivative
  2374. hess = optimize.BFGS()
  2375. x0 = np.array([1.0, 0.5])
  2376. scalar_function = ScalarFunction(
  2377. memoized_function, x0, (), jac, hess, None, None)
  2378. assert function_with_gradient.number_of_calls.c == 1
  2379. scalar_function.fun(x0 + 0.1)
  2380. assert function_with_gradient.number_of_calls.c == 2
  2381. scalar_function.fun(x0 + 0.2)
  2382. assert function_with_gradient.number_of_calls.c == 3
  2383. def test_gh12696():
  2384. # Test that optimize doesn't throw warning gh-12696
  2385. with assert_no_warnings():
  2386. optimize.fminbound(
  2387. lambda x: np.array([x**2]), -np.pi, np.pi, disp=False)
  2388. # --- Test minimize with equal upper and lower bounds --- #
  2389. def setup_test_equal_bounds():
  2390. # the success of test_equal_bounds depends on the exact seed
  2391. rng = np.random.default_rng(12223)
  2392. x0 = rng.random(4)
  2393. lb = np.array([0, 2, -1, -1.0])
  2394. ub = np.array([3, 2, 2, -1.0])
  2395. i_eb = (lb == ub)
  2396. def check_x(x, check_size=True, check_values=True):
  2397. if check_size:
  2398. assert x.size == 4
  2399. if check_values:
  2400. assert_allclose(x[i_eb], lb[i_eb])
  2401. def func(x):
  2402. check_x(x)
  2403. return optimize.rosen(x)
  2404. def grad(x):
  2405. check_x(x)
  2406. return optimize.rosen_der(x)
  2407. def callback(x, *args):
  2408. check_x(x)
  2409. def callback2(intermediate_result):
  2410. assert isinstance(intermediate_result, OptimizeResult)
  2411. check_x(intermediate_result.x)
  2412. def constraint1(x):
  2413. check_x(x, check_values=False)
  2414. return x[0:1] - 1
  2415. def jacobian1(x):
  2416. check_x(x, check_values=False)
  2417. dc = np.zeros_like(x)
  2418. dc[0] = 1
  2419. return dc
  2420. def constraint2(x):
  2421. check_x(x, check_values=False)
  2422. return x[2:3] - 0.5
  2423. def jacobian2(x):
  2424. check_x(x, check_values=False)
  2425. dc = np.zeros_like(x)
  2426. dc[2] = 1
  2427. return dc
  2428. c1a = NonlinearConstraint(constraint1, -np.inf, 0)
  2429. c1b = NonlinearConstraint(constraint1, -np.inf, 0, jacobian1)
  2430. c2a = NonlinearConstraint(constraint2, -np.inf, 0)
  2431. c2b = NonlinearConstraint(constraint2, -np.inf, 0, jacobian2)
  2432. # test using the three methods that accept bounds, use derivatives, and
  2433. # have some trouble when bounds fix variables
  2434. methods = ('L-BFGS-B', 'SLSQP', 'TNC')
  2435. # test w/out gradient, w/ gradient, and w/ combined objective/gradient
  2436. kwds = ({"fun": func, "jac": False},
  2437. {"fun": func, "jac": grad},
  2438. {"fun": (lambda x: (func(x), grad(x))),
  2439. "jac": True})
  2440. # test with both old- and new-style bounds
  2441. bound_types = (lambda lb, ub: list(zip(lb, ub)),
  2442. Bounds)
  2443. # Test for many combinations of constraints w/ and w/out jacobian
  2444. # Pairs in format: (test constraints, reference constraints)
  2445. # (always use analytical jacobian in reference)
  2446. constraints = ((None, None), ([], []),
  2447. (c1a, c1b), (c2b, c2b),
  2448. ([c1b], [c1b]), ([c2a], [c2b]),
  2449. ([c1a, c2a], [c1b, c2b]),
  2450. ([c1a, c2b], [c1b, c2b]),
  2451. ([c1b, c2b], [c1b, c2b]))
  2452. # test with and without callback function
  2453. callbacks = (None, callback, callback2)
  2454. data = {"methods": methods, "kwds": kwds, "bound_types": bound_types,
  2455. "constraints": constraints, "callbacks": callbacks,
  2456. "lb": lb, "ub": ub, "x0": x0, "i_eb": i_eb}
  2457. return data
  2458. eb_data = setup_test_equal_bounds()
  2459. # This test is about handling fixed variables, not the accuracy of the solvers
  2460. @pytest.mark.xfail_on_32bit("Failures due to floating point issues, not logic")
  2461. @pytest.mark.xfail(scipy.show_config(mode='dicts')['Compilers']['fortran']['name'] ==
  2462. "intel-llvm",
  2463. reason="Failures due to floating point issues, not logic")
  2464. @pytest.mark.parametrize('method', eb_data["methods"])
  2465. @pytest.mark.parametrize('kwds', eb_data["kwds"])
  2466. @pytest.mark.parametrize('bound_type', eb_data["bound_types"])
  2467. @pytest.mark.parametrize('constraints', eb_data["constraints"])
  2468. @pytest.mark.parametrize('callback', eb_data["callbacks"])
  2469. def test_equal_bounds(method, kwds, bound_type, constraints, callback):
  2470. """
  2471. Tests that minimizers still work if (bounds.lb == bounds.ub).any()
  2472. gh12502 - Divide by zero in Jacobian numerical differentiation when
  2473. equality bounds constraints are used
  2474. """
  2475. # GH-15051; slightly more skips than necessary; hopefully fixed by GH-14882
  2476. if (platform.machine() == 'aarch64' and method == "TNC"
  2477. and kwds["jac"] is False and callback is not None):
  2478. pytest.skip('Tolerance violation on aarch')
  2479. lb, ub = eb_data["lb"], eb_data["ub"]
  2480. x0, i_eb = eb_data["x0"], eb_data["i_eb"]
  2481. test_constraints, reference_constraints = constraints
  2482. if test_constraints and not method == 'SLSQP':
  2483. pytest.skip('Only SLSQP supports nonlinear constraints')
  2484. if method in ['SLSQP', 'TNC'] and callable(callback):
  2485. sig = inspect.signature(callback)
  2486. if 'intermediate_result' in set(sig.parameters):
  2487. pytest.skip("SLSQP, TNC don't support intermediate_result")
  2488. # reference constraints always have analytical jacobian
  2489. # if test constraints are not the same, we'll need finite differences
  2490. fd_needed = (test_constraints != reference_constraints)
  2491. bounds = bound_type(lb, ub) # old- or new-style
  2492. kwds.update({"x0": x0, "method": method, "bounds": bounds,
  2493. "constraints": test_constraints, "callback": callback})
  2494. res = optimize.minimize(**kwds)
  2495. expected = optimize.minimize(optimize.rosen, x0, method=method,
  2496. jac=optimize.rosen_der, bounds=bounds,
  2497. constraints=reference_constraints)
  2498. # compare the output of a solution with FD vs that of an analytic grad
  2499. assert res.success
  2500. assert_allclose(res.fun, expected.fun, rtol=5.0e-6)
  2501. assert_allclose(res.x, expected.x, rtol=5e-4)
  2502. if fd_needed or kwds['jac'] is False:
  2503. expected.jac[i_eb] = np.nan
  2504. assert res.jac.shape[0] == 4
  2505. assert_allclose(res.jac[i_eb], expected.jac[i_eb], rtol=1e-6)
  2506. if not (kwds['jac'] or test_constraints or isinstance(bounds, Bounds)):
  2507. # compare the output to an equivalent FD minimization that doesn't
  2508. # need factorization
  2509. def fun(x):
  2510. new_x = np.array([np.nan, 2, np.nan, -1])
  2511. new_x[[0, 2]] = x
  2512. return optimize.rosen(new_x)
  2513. fd_res = optimize.minimize(fun,
  2514. x0[[0, 2]],
  2515. method=method,
  2516. bounds=bounds[::2])
  2517. assert_allclose(res.fun, fd_res.fun)
  2518. # TODO this test should really be equivalent to factorized version
  2519. # above, down to res.nfev. However, testing found that when TNC is
  2520. # called with or without a callback the output is different. The two
  2521. # should be the same! This indicates that the TNC callback may be
  2522. # mutating something when it shouldn't.
  2523. assert_allclose(res.x[[0, 2]], fd_res.x, rtol=2e-6)
  2524. @pytest.mark.parametrize('method', eb_data["methods"])
  2525. def test_all_bounds_equal(method):
  2526. # this only tests methods that have parameters factored out when lb==ub
  2527. # it does not test other methods that work with bounds
  2528. def f(x, p1=1):
  2529. return np.linalg.norm(x) + p1
  2530. bounds = [(1, 1), (2, 2)]
  2531. x0 = (1.0, 3.0)
  2532. res = optimize.minimize(f, x0, bounds=bounds, method=method)
  2533. assert res.success
  2534. assert_allclose(res.fun, f([1.0, 2.0]))
  2535. assert res.nfev == 1
  2536. assert res.message == 'All independent variables were fixed by bounds.'
  2537. args = (2,)
  2538. res = optimize.minimize(f, x0, bounds=bounds, method=method, args=args)
  2539. assert res.success
  2540. assert_allclose(res.fun, f([1.0, 2.0], 2))
  2541. if method.upper() == 'SLSQP':
  2542. def con(x):
  2543. return np.sum(x)
  2544. nlc = NonlinearConstraint(con, -np.inf, 0.0)
  2545. res = optimize.minimize(
  2546. f, x0, bounds=bounds, method=method, constraints=[nlc]
  2547. )
  2548. assert res.success is False
  2549. assert_allclose(res.fun, f([1.0, 2.0]))
  2550. assert res.nfev == 1
  2551. message = "All independent variables were fixed by bounds, but"
  2552. assert res.message.startswith(message)
  2553. nlc = NonlinearConstraint(con, -np.inf, 4)
  2554. res = optimize.minimize(
  2555. f, x0, bounds=bounds, method=method, constraints=[nlc]
  2556. )
  2557. assert res.success is True
  2558. assert_allclose(res.fun, f([1.0, 2.0]))
  2559. assert res.nfev == 1
  2560. message = "All independent variables were fixed by bounds at values"
  2561. assert res.message.startswith(message)
  2562. def test_eb_constraints():
  2563. # make sure constraint functions aren't overwritten when equal bounds
  2564. # are employed, and a parameter is factored out. GH14859
  2565. def f(x):
  2566. return x[0]**3 + x[1]**2 + x[2]*x[3]
  2567. def cfun(x):
  2568. return x[0] + x[1] + x[2] + x[3] - 40
  2569. constraints = [{'type': 'ineq', 'fun': cfun}]
  2570. bounds = [(0, 20)] * 4
  2571. bounds[1] = (5, 5)
  2572. optimize.minimize(
  2573. f,
  2574. x0=[1, 2, 3, 4],
  2575. method='SLSQP',
  2576. bounds=bounds,
  2577. constraints=constraints,
  2578. )
  2579. assert constraints[0]['fun'] == cfun
  2580. def test_show_options():
  2581. solver_methods = {
  2582. 'minimize': MINIMIZE_METHODS,
  2583. 'minimize_scalar': MINIMIZE_SCALAR_METHODS,
  2584. 'root': ROOT_METHODS,
  2585. 'root_scalar': ROOT_SCALAR_METHODS,
  2586. 'linprog': LINPROG_METHODS,
  2587. 'quadratic_assignment': QUADRATIC_ASSIGNMENT_METHODS,
  2588. }
  2589. for solver, methods in solver_methods.items():
  2590. for method in methods:
  2591. # testing that `show_options` works without error
  2592. show_options(solver, method)
  2593. unknown_solver_method = {
  2594. 'minimize': "ekki", # unknown method
  2595. 'maximize': "cg", # unknown solver
  2596. 'maximize_scalar': "ekki", # unknown solver and method
  2597. }
  2598. for solver, method in unknown_solver_method.items():
  2599. # testing that `show_options` raises ValueError
  2600. assert_raises(ValueError, show_options, solver, method)
  2601. def test_bounds_with_list():
  2602. # gh13501. Bounds created with lists weren't working for Powell.
  2603. bounds = optimize.Bounds(lb=[5., 5.], ub=[10., 10.])
  2604. optimize.minimize(
  2605. optimize.rosen, x0=np.array([9, 9]), method='Powell', bounds=bounds
  2606. )
  2607. @pytest.mark.parametrize('method', (
  2608. 'slsqp', 'cg', 'cobyqa', 'powell','nelder-mead', 'bfgs', 'l-bfgs-b',
  2609. 'trust-constr'))
  2610. def test_minimize_maxiter_noninteger(method):
  2611. # Regression test for gh-23430
  2612. x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])
  2613. optimize.minimize(rosen, x0, method=method, options={'maxiter': 100.1})
  2614. def test_x_overwritten_user_function():
  2615. # if the user overwrites the x-array in the user function it's likely
  2616. # that the minimizer stops working properly.
  2617. # gh13740
  2618. def fquad(x):
  2619. a = np.arange(np.size(x))
  2620. x -= a
  2621. x *= x
  2622. return np.sum(x)
  2623. def fquad_jac(x):
  2624. a = np.arange(np.size(x))
  2625. x *= 2
  2626. x -= 2 * a
  2627. return x
  2628. def fquad_hess(x):
  2629. return np.eye(np.size(x)) * 2.0
  2630. meth_jac = [
  2631. 'newton-cg', 'dogleg', 'trust-ncg', 'trust-exact',
  2632. 'trust-krylov', 'trust-constr'
  2633. ]
  2634. meth_hess = [
  2635. 'dogleg', 'trust-ncg', 'trust-exact', 'trust-krylov', 'trust-constr'
  2636. ]
  2637. x0 = np.ones(5) * 1.5
  2638. for meth in MINIMIZE_METHODS:
  2639. jac = None
  2640. hess = None
  2641. if meth in meth_jac:
  2642. jac = fquad_jac
  2643. if meth in meth_hess:
  2644. hess = fquad_hess
  2645. res = optimize.minimize(fquad, x0, method=meth, jac=jac, hess=hess)
  2646. assert_allclose(res.x, np.arange(np.size(x0)), atol=2e-4)
  2647. class TestGlobalOptimization:
  2648. def test_optimize_result_attributes(self):
  2649. def func(x):
  2650. return x ** 2
  2651. # Note that `brute` solver does not return `OptimizeResult`
  2652. results = [optimize.basinhopping(func, x0=1),
  2653. optimize.differential_evolution(func, [(-4, 4)]),
  2654. optimize.shgo(func, [(-4, 4)]),
  2655. optimize.dual_annealing(func, [(-4, 4)]),
  2656. optimize.direct(func, [(-4, 4)]),
  2657. ]
  2658. for result in results:
  2659. assert isinstance(result, optimize.OptimizeResult)
  2660. assert hasattr(result, "x")
  2661. assert hasattr(result, "success")
  2662. assert hasattr(result, "message")
  2663. assert hasattr(result, "fun")
  2664. assert hasattr(result, "nfev")
  2665. assert hasattr(result, "nit")
  2666. def test_approx_fprime():
  2667. # check that approx_fprime (serviced by approx_derivative) works for
  2668. # jac and hess
  2669. g = optimize.approx_fprime(himmelblau_x0, himmelblau)
  2670. assert_allclose(g, himmelblau_grad(himmelblau_x0), rtol=5e-6)
  2671. h = optimize.approx_fprime(himmelblau_x0, himmelblau_grad)
  2672. assert_allclose(h, himmelblau_hess(himmelblau_x0), rtol=5e-6)
  2673. def test_gh12594():
  2674. # gh-12594 reported an error in `_linesearch_powell` and
  2675. # `_line_for_search` when `Bounds` was passed lists instead of arrays.
  2676. # Check that results are the same whether the inputs are lists or arrays.
  2677. def f(x):
  2678. return x[0]**2 + (x[1] - 1)**2
  2679. bounds = Bounds(lb=[-10, -10], ub=[10, 10])
  2680. res = optimize.minimize(f, x0=(0, 0), method='Powell', bounds=bounds)
  2681. bounds = Bounds(lb=np.array([-10, -10]), ub=np.array([10, 10]))
  2682. ref = optimize.minimize(f, x0=(0, 0), method='Powell', bounds=bounds)
  2683. assert_allclose(res.fun, ref.fun)
  2684. assert_allclose(res.x, ref.x)
  2685. def test_gh12513_trustregion_exact_infinite_loop():
  2686. # gh-12513 reported that optimize.minimize might hang when
  2687. # method='trust-exact', using the option ``subproblem_maxiter``,
  2688. # this can be avoided.
  2689. H = np.array(
  2690. [[3.67335930e01, -2.52334820e02, 1.15477558e01, -1.19933725e-03,
  2691. -2.06408851e03, -2.05821411e00, -2.52334820e02, -6.52076924e02,
  2692. -2.71362566e-01, -1.98885126e00, 1.22085415e00, 2.30220713e00,
  2693. -9.71278532e-02, -5.11210123e-01, -1.00399562e00, 1.43319679e-01,
  2694. 6.03815471e00, -6.38719934e-02, 1.65623929e-01],
  2695. [-2.52334820e02, 1.76757312e03, -9.92814996e01, 1.06533600e-02,
  2696. 1.44442941e04, 1.43811694e01, 1.76757312e03, 4.56694461e03,
  2697. 2.22263363e00, 1.62977318e01, -7.81539315e00, -1.24938012e01,
  2698. 6.74029088e-01, 3.22802671e00, 5.14978971e00, -9.58561209e-01,
  2699. -3.92199895e01, 4.47201278e-01, -1.17866744e00],
  2700. [1.15477558e01, -9.92814996e01, 3.63872363e03, -4.40007197e-01,
  2701. -9.55435081e02, -1.13985105e00, -9.92814996e01, -2.58307255e02,
  2702. -5.21335218e01, -3.77485107e02, -6.75338369e01, -1.89457169e02,
  2703. 5.67828623e00, 5.82402681e00, 1.72734354e01, -4.29114840e00,
  2704. -7.84885258e01, 3.17594634e00, 2.45242852e00],
  2705. [-1.19933725e-03, 1.06533600e-02, -4.40007197e-01, 5.73576663e-05,
  2706. 1.01563710e-01, 1.18838745e-04, 1.06533600e-02, 2.76535767e-02,
  2707. 6.25788669e-03, 4.50699620e-02, 8.64152333e-03, 2.27772377e-02,
  2708. -8.51026855e-04, 1.65316383e-04, 1.38977551e-03, 5.51629259e-04,
  2709. 1.38447755e-02, -5.17956723e-04, -1.29260347e-04],
  2710. [-2.06408851e03, 1.44442941e04, -9.55435081e02, 1.01563710e-01,
  2711. 1.23101825e05, 1.26467259e02, 1.44442941e04, 3.74590279e04,
  2712. 2.18498571e01, 1.60254460e02, -7.52977260e01, -1.17989623e02,
  2713. 6.58253160e00, 3.14949206e01, 4.98527190e01, -9.33338661e00,
  2714. -3.80465752e02, 4.33872213e00, -1.14768816e01],
  2715. [-2.05821411e00, 1.43811694e01, -1.13985105e00, 1.18838745e-04,
  2716. 1.26467259e02, 1.46226198e-01, 1.43811694e01, 3.74509252e01,
  2717. 2.76928748e-02, 2.03023837e-01, -8.84279903e-02, -1.29523344e-01,
  2718. 8.06424434e-03, 3.83330661e-02, 5.81579023e-02, -1.12874980e-02,
  2719. -4.48118297e-01, 5.15022284e-03, -1.41501894e-02],
  2720. [-2.52334820e02, 1.76757312e03, -9.92814996e01, 1.06533600e-02,
  2721. 1.44442941e04, 1.43811694e01, 1.76757312e03, 4.56694461e03,
  2722. 2.22263363e00, 1.62977318e01, -7.81539315e00, -1.24938012e01,
  2723. 6.74029088e-01, 3.22802671e00, 5.14978971e00, -9.58561209e-01,
  2724. -3.92199895e01, 4.47201278e-01, -1.17866744e00],
  2725. [-6.52076924e02, 4.56694461e03, -2.58307255e02, 2.76535767e-02,
  2726. 3.74590279e04, 3.74509252e01, 4.56694461e03, 1.18278398e04,
  2727. 5.82242837e00, 4.26867612e01, -2.03167952e01, -3.22894255e01,
  2728. 1.75705078e00, 8.37153730e00, 1.32246076e01, -2.49238529e00,
  2729. -1.01316422e02, 1.16165466e00, -3.09390862e00],
  2730. [-2.71362566e-01, 2.22263363e00, -5.21335218e01, 6.25788669e-03,
  2731. 2.18498571e01, 2.76928748e-02, 2.22263363e00, 5.82242837e00,
  2732. 4.36278066e01, 3.14836583e02, -2.04747938e01, -3.05535101e01,
  2733. -1.24881456e-01, 1.15775394e01, 4.06907410e01, -1.39317748e00,
  2734. -3.90902798e01, -9.71716488e-02, 1.06851340e-01],
  2735. [-1.98885126e00, 1.62977318e01, -3.77485107e02, 4.50699620e-02,
  2736. 1.60254460e02, 2.03023837e-01, 1.62977318e01, 4.26867612e01,
  2737. 3.14836583e02, 2.27255216e03, -1.47029712e02, -2.19649109e02,
  2738. -8.83963155e-01, 8.28571708e01, 2.91399776e02, -9.97382920e00,
  2739. -2.81069124e02, -6.94946614e-01, 7.38151960e-01],
  2740. [1.22085415e00, -7.81539315e00, -6.75338369e01, 8.64152333e-03,
  2741. -7.52977260e01, -8.84279903e-02, -7.81539315e00, -2.03167952e01,
  2742. -2.04747938e01, -1.47029712e02, 7.83372613e01, 1.64416651e02,
  2743. -4.30243758e00, -2.59579610e01, -6.25644064e01, 6.69974667e00,
  2744. 2.31011701e02, -2.68540084e00, 5.44531151e00],
  2745. [2.30220713e00, -1.24938012e01, -1.89457169e02, 2.27772377e-02,
  2746. -1.17989623e02, -1.29523344e-01, -1.24938012e01, -3.22894255e01,
  2747. -3.05535101e01, -2.19649109e02, 1.64416651e02, 3.75893031e02,
  2748. -7.42084715e00, -4.56437599e01, -1.11071032e02, 1.18761368e01,
  2749. 4.78724142e02, -5.06804139e00, 8.81448081e00],
  2750. [-9.71278532e-02, 6.74029088e-01, 5.67828623e00, -8.51026855e-04,
  2751. 6.58253160e00, 8.06424434e-03, 6.74029088e-01, 1.75705078e00,
  2752. -1.24881456e-01, -8.83963155e-01, -4.30243758e00, -7.42084715e00,
  2753. 9.62009425e-01, 1.53836355e00, 2.23939458e00, -8.01872920e-01,
  2754. -1.92191084e01, 3.77713908e-01, -8.32946970e-01],
  2755. [-5.11210123e-01, 3.22802671e00, 5.82402681e00, 1.65316383e-04,
  2756. 3.14949206e01, 3.83330661e-02, 3.22802671e00, 8.37153730e00,
  2757. 1.15775394e01, 8.28571708e01, -2.59579610e01, -4.56437599e01,
  2758. 1.53836355e00, 2.63851056e01, 7.34859767e01, -4.39975402e00,
  2759. -1.12015747e02, 5.11542219e-01, -2.64962727e00],
  2760. [-1.00399562e00, 5.14978971e00, 1.72734354e01, 1.38977551e-03,
  2761. 4.98527190e01, 5.81579023e-02, 5.14978971e00, 1.32246076e01,
  2762. 4.06907410e01, 2.91399776e02, -6.25644064e01, -1.11071032e02,
  2763. 2.23939458e00, 7.34859767e01, 2.36535458e02, -1.09636675e01,
  2764. -2.72152068e02, 6.65888059e-01, -6.29295273e00],
  2765. [1.43319679e-01, -9.58561209e-01, -4.29114840e00, 5.51629259e-04,
  2766. -9.33338661e00, -1.12874980e-02, -9.58561209e-01, -2.49238529e00,
  2767. -1.39317748e00, -9.97382920e00, 6.69974667e00, 1.18761368e01,
  2768. -8.01872920e-01, -4.39975402e00, -1.09636675e01, 1.16820748e00,
  2769. 3.00817252e01, -4.51359819e-01, 9.82625204e-01],
  2770. [6.03815471e00, -3.92199895e01, -7.84885258e01, 1.38447755e-02,
  2771. -3.80465752e02, -4.48118297e-01, -3.92199895e01, -1.01316422e02,
  2772. -3.90902798e01, -2.81069124e02, 2.31011701e02, 4.78724142e02,
  2773. -1.92191084e01, -1.12015747e02, -2.72152068e02, 3.00817252e01,
  2774. 1.13232557e03, -1.33695932e01, 2.22934659e01],
  2775. [-6.38719934e-02, 4.47201278e-01, 3.17594634e00, -5.17956723e-04,
  2776. 4.33872213e00, 5.15022284e-03, 4.47201278e-01, 1.16165466e00,
  2777. -9.71716488e-02, -6.94946614e-01, -2.68540084e00, -5.06804139e00,
  2778. 3.77713908e-01, 5.11542219e-01, 6.65888059e-01, -4.51359819e-01,
  2779. -1.33695932e01, 4.27994168e-01, -5.09020820e-01],
  2780. [1.65623929e-01, -1.17866744e00, 2.45242852e00, -1.29260347e-04,
  2781. -1.14768816e01, -1.41501894e-02, -1.17866744e00, -3.09390862e00,
  2782. 1.06851340e-01, 7.38151960e-01, 5.44531151e00, 8.81448081e00,
  2783. -8.32946970e-01, -2.64962727e00, -6.29295273e00, 9.82625204e-01,
  2784. 2.22934659e01, -5.09020820e-01, 4.09964606e00]]
  2785. )
  2786. J = np.array([
  2787. -2.53298102e-07, 1.76392040e-06, 1.74776130e-06, -4.19479903e-10,
  2788. 1.44167498e-05, 1.41703911e-08, 1.76392030e-06, 4.96030153e-06,
  2789. -2.35771675e-07, -1.68844985e-06, 4.29218258e-07, 6.65445159e-07,
  2790. -3.87045830e-08, -3.17236594e-07, -1.21120169e-06, 4.59717313e-08,
  2791. 1.67123246e-06, 1.46624675e-08, 4.22723383e-08
  2792. ])
  2793. def fun(x):
  2794. return np.dot(np.dot(x, H), x) / 2 + np.dot(x, J)
  2795. def jac(x):
  2796. return np.dot(x, H) + J
  2797. def hess(x):
  2798. return H
  2799. x0 = np.zeros(19)
  2800. res = optimize.minimize(
  2801. fun,
  2802. x0,
  2803. jac=jac,
  2804. hess=hess,
  2805. method="trust-exact",
  2806. options={"gtol": 1e-6, "subproblem_maxiter": 10},
  2807. )
  2808. assert res.success
  2809. assert abs(fun(res.x)) < 1e-5
  2810. @pytest.mark.parametrize('method', ['Newton-CG', 'trust-constr'])
  2811. @pytest.mark.parametrize('sparse_type', [coo_matrix, csc_matrix, csr_matrix,
  2812. coo_array, csr_array, csc_array])
  2813. def test_sparse_hessian(method, sparse_type):
  2814. # gh-8792 reported an error for minimization with `newton_cg` when `hess`
  2815. # returns a sparse array. Check that results are the same whether `hess`
  2816. # returns a dense or sparse array for optimization methods that accept
  2817. # sparse Hessian matrices.
  2818. def sparse_rosen_hess(x):
  2819. return sparse_type(rosen_hess(x))
  2820. x0 = [2., 2.]
  2821. res_sparse = optimize.minimize(rosen, x0, method=method,
  2822. jac=rosen_der, hess=sparse_rosen_hess)
  2823. res_dense = optimize.minimize(rosen, x0, method=method,
  2824. jac=rosen_der, hess=rosen_hess)
  2825. assert_allclose(res_dense.fun, res_sparse.fun)
  2826. assert_allclose(res_dense.x, res_sparse.x)
  2827. assert res_dense.nfev == res_sparse.nfev
  2828. assert res_dense.njev == res_sparse.njev
  2829. assert res_dense.nhev == res_sparse.nhev
  2830. @pytest.mark.parametrize('workers', [None, 2])
  2831. @pytest.mark.parametrize(
  2832. 'method',
  2833. ['l-bfgs-b',
  2834. 'bfgs',
  2835. 'slsqp',
  2836. 'trust-constr',
  2837. 'Newton-CG',
  2838. 'CG',
  2839. 'tnc',
  2840. 'trust-ncg',
  2841. 'trust-krylov'])
  2842. class TestWorkers:
  2843. def setup_method(self):
  2844. self.x0 = np.array([1.0, 2.0, 3.0])
  2845. def test_smoke(self, workers, method):
  2846. # checks parallelised optimization output is same as serial
  2847. workers = workers or map
  2848. kwds = {'jac': None, 'hess': None}
  2849. if method in ['Newton-CG', 'trust-ncg', 'trust-krylov']:
  2850. # methods that require a callable jac
  2851. kwds['jac'] = rosen_der
  2852. kwds['hess'] = '2-point'
  2853. with MapWrapper(workers) as mf:
  2854. res = optimize.minimize(
  2855. rosen, self.x0, options={"workers":mf}, method=method, **kwds
  2856. )
  2857. res_default = optimize.minimize(
  2858. rosen, self.x0, method=method, **kwds
  2859. )
  2860. assert_equal(res.x, res_default.x)
  2861. assert_equal(res.nfev, res_default.nfev)
  2862. def test_equal_bounds(self, workers, method):
  2863. workers = workers or map
  2864. if method not in ['l-bfgs-b', 'slsqp', 'trust-constr', 'tnc']:
  2865. pytest.skip(f"{method} cannot use bounds")
  2866. bounds = Bounds([0, 2.0, 0.], [10., 2.0, 10.])
  2867. with MapWrapper(workers) as mf:
  2868. res = optimize.minimize(
  2869. rosen, self.x0, bounds=bounds, options={"workers": mf}, method=method
  2870. )
  2871. assert res.success
  2872. assert_allclose(res.x[1], 2.0)
  2873. # Tests for PEP 649/749 style annotations in callback and objective functions
  2874. if sys.version_info < (3, 14):
  2875. from typing import Any
  2876. _DUMMY_TYPE = Any
  2877. else:
  2878. import typing
  2879. if typing.TYPE_CHECKING:
  2880. _DUMMY_TYPE = typing.Any
  2881. def rosen_annotated(x: _DUMMY_TYPE) -> float:
  2882. return rosen(x) + 1
  2883. def rosen_der_annotated(x: _DUMMY_TYPE) -> _DUMMY_TYPE:
  2884. return rosen_der(x)
  2885. def rosen_hess_annotated(x: _DUMMY_TYPE) -> _DUMMY_TYPE:
  2886. return rosen_hess(x)
  2887. def callable_annotated(intermediate_result: _DUMMY_TYPE) -> None:
  2888. pass
  2889. @pytest.mark.skipif(sys.version_info < (3, 14),
  2890. reason="Requires PEP 649/749 from Python 3.14+.")
  2891. class TestAnnotations:
  2892. def setup_method(self):
  2893. self.x0 = np.array([1.0, 1.01])
  2894. self.brute_params = (2, 3, 7, 8, 9, 10, 44, -1, 2, 26, 1, -2, 0.5)
  2895. @pytest.mark.parametrize("method", [
  2896. 'Nelder-Mead',
  2897. 'Powell',
  2898. 'CG',
  2899. 'bfgs',
  2900. 'Newton-CG',
  2901. 'l-bfgs-b',
  2902. 'tnc',
  2903. 'COBYLA',
  2904. # 'COBYQA', # External module. Will trigger NameError, skip for now
  2905. 'slsqp',
  2906. 'trust-constr',
  2907. 'dogleg',
  2908. 'trust-ncg',
  2909. 'trust-exact',
  2910. 'trust-krylov'
  2911. ])
  2912. def test_callable_annotations(self, method):
  2913. kwds = {'jac': None, 'hess': None, 'callback': callable_annotated}
  2914. if method in ['CG', 'BFGS', 'Newton-CG', "L-BFGS-B", 'TNC', 'SLSQP', 'dogleg',
  2915. 'trust-ncg', 'trust-krylov', 'trust-exact', 'trust-constr']:
  2916. # methods that require a callable jac
  2917. kwds['jac'] = rosen_der_annotated
  2918. if method in ['Newton-CG', 'dogleg', 'trust-ncg', 'trust-exact',
  2919. 'trust-krylov', 'trust-constr']:
  2920. kwds['hess'] = rosen_hess_annotated
  2921. optimize.minimize(rosen_annotated, self.x0, method=method, **kwds)
  2922. def test_differential_evolution_annotations(self):
  2923. bounds = [(-5, 5), (-5, 5)]
  2924. res = optimize.differential_evolution(rosen_annotated, bounds, seed=1,
  2925. callback=callable_annotated)
  2926. assert res.success, f"Unexpected error: {res.message}"
  2927. def test_curve_fit_annotations(self):
  2928. def model_func(x: _DUMMY_TYPE, a: float, b, c) -> _DUMMY_TYPE:
  2929. return a * np.exp(-b * x) + c
  2930. def model_jac(x: _DUMMY_TYPE, a: float, b, c) -> _DUMMY_TYPE:
  2931. return np.array([
  2932. np.exp(-b * x),
  2933. -a * x * np.exp(-b * x),
  2934. np.ones_like(x)
  2935. ]).T
  2936. xdata = np.linspace(0, 4, 10)
  2937. ydata = model_func(xdata, 2.5, 1.3, 0.5)
  2938. _,_,_,_,res = optimize.curve_fit(model_func, xdata, ydata, jac=model_jac,
  2939. full_output=True)
  2940. assert (res in [1, 2, 3, 4]), f"Unexpected error: {res.message}"
  2941. def test_brute_annotations(self):
  2942. def f1(z: _DUMMY_TYPE, *params: float) -> float:
  2943. x, y = z
  2944. a, b, c, d, e, f, g, h, i, j, k, l, scale = params
  2945. return (a * x**2 + b * x * y + c * y**2 + d*x + e*y + f)
  2946. def annotated_fmin(callable: _DUMMY_TYPE, x0: _DUMMY_TYPE,
  2947. *args, **kwargs) -> _DUMMY_TYPE:
  2948. return optimize.fmin(callable, x0, *args, **kwargs)
  2949. rranges = (slice(-4, 4, 0.25), slice(-4, 4, 0.25))
  2950. optimize.brute(f1, rranges, args=self.brute_params, finish=annotated_fmin)
  2951. def test_basinhopping_annotations(self):
  2952. # NOTE: basinhopping callback does not match
  2953. # callback(intermediate_result: OptimizeResult)
  2954. # signature. Consider adding when updated.
  2955. def acceptable_test(f_new: float, x_new: _DUMMY_TYPE,
  2956. f_old: float,x_old: _DUMMY_TYPE) -> bool:
  2957. return True
  2958. res = optimize.basinhopping(rosen_annotated, self.x0, niter=2, seed=1,
  2959. accept_test=acceptable_test)
  2960. assert res.success, f"Unexpected error: {res.message}"
  2961. def test_multiprocessing_too_many_open_files_23080():
  2962. # https://github.com/scipy/scipy/issues/23080
  2963. x0 = np.array([0.9, 0.9])
  2964. # check that ScalarHessWrapper doesn't keep pool object alive
  2965. with assert_deallocated(multiprocessing.Pool, 2) as pool_obj:
  2966. with pool_obj as p:
  2967. _minimize_bfgs(rosen, x0, workers=p.map)
  2968. del p
  2969. del pool_obj