{"id":1488,"date":"2010-09-21T19:27:27","date_gmt":"2010-09-21T18:27:27","guid":{"rendered":"http:\/\/www.walkingrandomly.com\/?p=1488"},"modified":"2010-09-22T02:16:35","modified_gmt":"2010-09-22T01:16:35","slug":"a-faster-version-of-matlabs-fsolve-using-the-nag-toolbox-for-matlab","status":"publish","type":"post","link":"https:\/\/walkingrandomly.com\/?p=1488","title":{"rendered":"A faster version of MATLAB&#8217;s fsolve using the NAG Toolbox for MATLAB"},"content":{"rendered":"<p>Part of my job at the <a href=\"http:\/\/www.manchester.ac.uk\/\">University of Manchester<\/a> is to help researchers improve their code in systems such as <a href=\"http:\/\/www.wolfram.com\/\">Mathematica<\/a>, <a href=\"http:\/\/www.mathworks.com\/\">MATLAB<\/a>, <a href=\"http:\/\/www.nag.co.uk\/\">NAG<\/a> and so on.\u00a0 One of the most common questions I get asked is<em> &#8216;Can you make this go any faster please?&#8217; <\/em>and it always makes my day if I manage to do something significant such as a speed up of a factor of 10 or more.\u00a0 The users, however, are often happy with a lot less and I once got bought a bottle of (rather good) wine for a mere 25% speed-up (Note:\u00a0 Gifts of wine aren&#8217;t mandatory)<\/p>\n<p>I employ numerous tricks to get these speed-ups including applying vectorisation, using mex files, modifying the algorithm to something more efficient,picking the brains of colleagues and so on.\u00a0 Over the last year or so, I have managed to help several researchers get significant speed-ups in their MATLAB programs by doing one simple thing &#8211; swapping the <a href=\"http:\/\/www.mathworks.de\/help\/toolbox\/optim\/ug\/fsolve.html\">fsolve<\/a> function for an equivalent from the <a href=\"http:\/\/www.nag.co.uk\/numeric\/MB\/start.asp\">NAG Toolbox for MATLAB<\/a>.<\/p>\n<p>The fsolve function is part of MATLAB&#8217;s <a href=\"http:\/\/www.mathworks.com\/products\/optimization\/\">optimisation toolbox<\/a> and, according to the documentation, it does the following:<\/p>\n<p><em>&#8220;fsolve Solves a system of nonlinear equation of the form F(X) = 0 where F and X  may be vectors or matrices.&#8221;<\/em><\/p>\n<p>The NAG  Toolbox for MATLAB contains a total of 6 different routines that can  perform similar calculations to fsolve.\u00a0 These 6 routines are split into  2 sets of 3 as follows<\/p>\n<p><strong>For when you  have function values only:<\/strong><br \/>\nc05nb \u2013 Solution of system of  nonlinear equations using function values only (easy-to-use)<br \/>\nc05nc \u2013 Solution of system of nonlinear equations using  function values only (comprehensive)<br \/>\nc05nd \u2013 Solution  of system of nonlinear equations using function values only (reverse  communication)<br \/>\n<strong> <\/strong><\/p>\n<p><strong>For when you have both  function values and first derivatives<\/strong><br \/>\nc05pb \u2013 Solution  of system of nonlinear equations using first derivatives (easy-to-use)<br \/>\nc05pc \u2013 Solution of system of nonlinear equations using  first derivatives (comprehensive)<br \/>\nc05pd \u2013 Solution of  system of nonlinear equations using first derivatives (reverse  communication)<\/p>\n<p>So, the NAG routine you  choose depends on whether or not you can supply first derivatives and  exactly which options you&#8217;d like to exercise.\u00a0 For many problems it will  come down to a choice between the two &#8216;easy to use&#8217; routines &#8211; c05nb  and c05pb (at least, they are the ones I&#8217;ve used most of the time)<\/p>\n<p>Let&#8217;s look at a simple  example.\u00a0 You&#8217;d like to find a root of the following system of  non-linear equations.<\/p>\n<p>F(1)=exp(-x(1))  + sinh(2*x(2)) + tanh(2*x(3)) &#8211; 5.01;<br \/>\nF(2)=exp(2*x(1)) +  sinh(-x(2) ) + tanh(2*x(3) ) &#8211; 5.85;<br \/>\nF(3)=exp(2*x(1)) +  sinh(2*x(2) ) + tanh(-x(3) ) &#8211; 8.88;<\/p>\n<p>i.e.  you want to find a vector X containing three values for which F(X)=0.<\/p>\n<p>To solve this using MATLAB and the optimisation toolbox you  could proceed as follows, first create a .m file called  <a href=\"https:\/\/www.walkingrandomly.com\/images\/NAG\/MATLAB\/fsolve\/fsolve_obj_MATLAB.m\">fsolve_obj_MATLAB.m<\/a> (henceforth referred to as the objective function)  that contains the following<\/p>\n<pre>function  F=fsolve_obj_MATLAB(x)\r\nF=zeros(1,3);\r\nF(1)=exp(-x(1))  + sinh(2*x(2)) + tanh(2*x(3)) - 5.01;\r\nF(2)=exp(2*x(1)) +  sinh(-x(2) ) + tanh(2*x(3) ) - 5.85;\r\nF(3)=exp(2*x(1)) +  sinh(2*x(2) ) + tanh(-x(3) ) - 8.88;\r\nend<\/pre>\n<p>Now type the following at the MATLAB  command prompt to actually solve the problem:<\/p>\n<pre>options=optimset('Display','off'); %Prevents fsolve from  displaying too much information\r\nstartX=[0 0 0]; %Our  starting guess for the solution vector\r\nX=fsolve(@fsolve_obj_MATLAB,startX,options)<\/pre>\n<p>MATLAB finds the following solution (Note that it&#8217;s only found one of the solutions, not all of them, which is typical of the type of algorithm used in fsolve)<\/p>\n<pre>X =\r\n0.9001\u00a0\u00a0\u00a0  1.0002\u00a0\u00a0\u00a0 1.0945<\/pre>\n<p>Since we are not supplying the derivatives of our objective function and we are not using any special options, it turns out to be very easy to switch from using the optimisation toolbox to the NAG toolbox for this particular problem by making use of the routine c05nb.<\/p>\n<p>First, we need  to change the header of our objective function&#8217;s .m file from<\/p>\n<pre>function F=fsolve_obj_MATLAB(x)<\/pre>\n<p>to<\/p>\n<pre>function  [F,iflag]=fsolve_obj_NAG(n,x,iflag)<\/pre>\n<p>so our new .m file, <a href=\"https:\/\/www.walkingrandomly.com\/images\/NAG\/MATLAB\/fsolve\/fsolve_obj_NAG.m\">fsolve_obj_NAG.m<\/a>, will contain the following<\/p>\n<pre>function  [F,iflag]=fsolve_obj_NAG(n,x,iflag)\r\nF=zeros(1,3);\r\nF(1)=exp(-x(1)) + sinh(2*x(2)) + tanh(2*x(3)) - 5.01;\r\nF(2)=exp(2*x(1)) + sinh(-x(2) ) + tanh(2*x(3) ) - 5.85;\r\nF(3)=exp(2*x(1)) + sinh(2*x(2) ) + tanh(-x(3) ) - 8.88;\r\nend<\/pre>\n<p>Note that the ONLY change we made to the objective function was the very first line.\u00a0 The  NAG routine c05nb expects to see some extra arguments in the objective function (iflag and n in this example) and it expects to see them in a very particular order but we are not obliged to use them.\u00a0 Using this modified objective function we can proceed as follows.<\/p>\n<pre>startX=[0 0 0]; %Our starting guess\r\nX=c05nb('fsolve_obj_NAG',startX)<\/pre>\n<p>NAG gives the same result as the optimisation toolbox:<\/p>\n<pre>X =\r\n0.9001\u00a0\u00a0\u00a0 1.0002\u00a0\u00a0\u00a0 1.0945<\/pre>\n<p>Let&#8217;s look at timings.\u00a0 I performed the calculations above on an 800Mhz laptop running Ubuntu Linux 10.04, MATLAB 2009 and Mark 22 of the NAG toolbox and got the following timings  (averaged over 10 runs):<\/p>\n<p>Optimisation  toolbox: 0.0414 seconds<br \/>\nNAG toolbox: 0.0021 seconds<\/p>\n<p>So, for this particular problem NAG was faster than MATLAB by a factor of 19.7 times on my machine.<\/p>\n<p>That&#8217;s all well and good, but this is a simple problem.  Does this scale to more realistic problems I hear you ask?  Well, the answer is &#8216;yes&#8217;.  I&#8217;ve used this technique several times now on production code and it almost always results in some kind of speed-up.  Not always 19.7 times faster, I&#8217;ll grant you, but usually enough to make the modifications worthwhile.<\/p>\n<p>I can&#8217;t actually post any of the more complicated examples here because the code in question belongs to other people but I was recently sent a piece of code from a researcher that made heavy use of fsolve and a typical run took 18.19 seconds (that&#8217;s for everything, not just the fsolve bit).\u00a0 Simply by swapping a couple of lines of code to make it  use the NAG toolbox rather than the optimisation toolbox I reduced the runtime to 1.08 seconds &#8211; a speed-up of almost 17 times.<\/p>\n<p>Sadly, this technique doesn&#8217;t always result in a speed-up and I&#8217;m working on figuring out exactly when the benefits disappear.  I guess that the main reason for NAG&#8217;s good performance is that it uses highly optimised, compiled Fortran code compared to MATLAB&#8217;s interpreted .m code.  One case where NAG didn&#8217;t help was for a massively complicated objective function and the majority of the run-time of the code was spent evaluating this function.  In this situation, NAG and MATLAB came out roughly neck and neck.<\/p>\n<p>In the meantime, if you have some code that you&#8217;d like me to try it on then drop me a line.<\/p>\n<p><strong>If you enjoyed this post then you may also like:<\/strong><\/p>\n<ul>\n<li><a href=\"https:\/\/www.walkingrandomly.com\/?p=1552\">A faster version of MATLAB&#8217;s interp1<\/a><\/li>\n<li><a href=\"https:\/\/www.walkingrandomly.com\/?p=2782\">An alternative to the ranksum function using the NAG Toolbox for MATLAB<\/a><\/li>\n<li><a href=\"https:\/\/www.walkingrandomly.com\/?p=2351\">An alternative to the binopdf function using the NAG Toolbox for MATLAB<\/a><\/li>\n<li><a href=\"https:\/\/www.walkingrandomly.com\/?p=2678\">A free version of the pdist command for MATLAB<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Part of my job at the University of Manchester is to help researchers improve their code in systems such as Mathematica, MATLAB, NAG and so on.\u00a0 One of the most common questions I get asked is &#8216;Can you make this go any faster please?&#8217; and it always makes my day if I manage to do [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[4,11,28,7],"tags":[],"class_list":["post-1488","post","type-post","status-publish","format-standard","hentry","category-math-software","category-matlab","category-nag-library","category-programming"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p3swhs-o0","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=\/wp\/v2\/posts\/1488","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1488"}],"version-history":[{"count":24,"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=\/wp\/v2\/posts\/1488\/revisions"}],"predecessor-version":[{"id":2901,"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=\/wp\/v2\/posts\/1488\/revisions\/2901"}],"wp:attachment":[{"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1488"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1488"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1488"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}