{"id":407,"date":"2016-01-27T16:14:59","date_gmt":"2016-01-27T16:14:59","guid":{"rendered":"http:\/\/drsfenner.org\/blog\/?p=407"},"modified":"2016-02-05T16:30:49","modified_gmt":"2016-02-05T16:30:49","slug":"into-the-weeds-iii-hacking-lapack-calls-to-save-memory","status":"publish","type":"post","link":"http:\/\/drsfenner.org\/blog\/2016\/01\/into-the-weeds-iii-hacking-lapack-calls-to-save-memory\/","title":{"rendered":"Into the Weeds III &#8211; Hacking LAPACK Calls To Save Memory"},"content":{"rendered":"<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<p>In the <a href=\"http:\/\/drsfenner.org\/blog\/2015\/12\/into-the-weeds-i-lapack-lapacke-and-three-paths\/\">last post<\/a>, we tried to use lower-level LAPACK\/LAPACKE calls to remove some of the copying overhead of our operations. Well, it turns out that it didn&#8217;t work out so well (see the table at the end of the post). The fundamental reason why is that LAPACKE took the easy way out, when they decided to wrap LAPACK functions. In particular, whenever there is a C-\/Fortran-ordering issue (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Row-major_order\">for a reminder<\/a>), LAPACKE simply makes a copy.<!--more--><\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<h3 id=\"spin-up\">Spin Up<\/h3>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing code_cell rendered\">\n<div class=\"input\">\n<div class=\"prompt input_prompt\">In&nbsp;[1]:<\/div>\n<div class=\"inner_cell\">\n<div class=\"input_area\">\n<div class=\" highlight hl-ipython2\">\n<pre><span class=\"kn\">import<\/span> <span class=\"nn\">numpy<\/span> <span class=\"kn\">as<\/span> <span class=\"nn\">np<\/span>\r\n\r\n<span class=\"kn\">from<\/span> <span class=\"nn\">shared<\/span> <span class=\"kn\">import<\/span> <span class=\"p\">(<\/span><span class=\"n\">lap<\/span><span class=\"p\">,<\/span> <span class=\"n\">get_dptr<\/span><span class=\"p\">,<\/span> <span class=\"n\">create_data1<\/span><span class=\"p\">,<\/span> <span class=\"n\">create_data2<\/span><span class=\"p\">,<\/span> <span class=\"n\">simple_args<\/span><span class=\"p\">,<\/span> <span class=\"n\">my_dlalsd<\/span><span class=\"p\">)<\/span>\r\n<span class=\"kn\">from<\/span> <span class=\"nn\">shared_dgelsd<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">make_structures_dgelsd<\/span>\r\n\r\n<span class=\"o\">%<\/span><span class=\"k\">load_ext<\/span> memory_profiler\r\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<h3 id=\"and-now-for-something-completely-different-...\">And now for something completely different &#8230;<\/h3>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<p>Here we have two re-worked methods for solving least squares using either Fortran (col-major) data or, here&#8217;s the fun part folks, solving the least squares problem with C (row-major) data by <em>calling the LAPACK\/E routines and lying<\/em>. We tell the Fortran routines that we have Fortran data (remember, in memory it is in <em>row-major<\/em> order) and we <em>solve a &quot;transposed&quot; problem<\/em>. That turns out to be just about trivial for using the <span class=\"math inline\">\\(QR\\)<\/span> based method because the function call accepts a &quot;T&quot; (transposed) argument.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing code_cell rendered\">\n<div class=\"input\">\n<div class=\"prompt input_prompt\">In&nbsp;[2]:<\/div>\n<div class=\"inner_cell\">\n<div class=\"input_area\">\n<div class=\" highlight hl-ipython2\">\n<pre><span class=\"k\">def<\/span> <span class=\"nf\">sls_qr_via_dgels<\/span><span class=\"p\">(<\/span><span class=\"n\">X<\/span><span class=\"p\">,<\/span><span class=\"n\">Y<\/span><span class=\"p\">,<\/span><span class=\"n\">trans<\/span><span class=\"p\">):<\/span>\r\n    <span class=\"k\">assert<\/span> <span class=\"n\">X<\/span><span class=\"o\">.<\/span><span class=\"n\">flags<\/span><span class=\"o\">.<\/span><span class=\"n\">f_contiguous<\/span> <span class=\"ow\">or<\/span> <span class=\"n\">trans<\/span> <span class=\"c\"># don&#39;t allow the copy case although it should work<\/span>\r\n    <span class=\"c\"># assert np.linalg.matrix_rank(X) == np.shape[1] # &quot;full column rank&quot;<\/span>\r\n\r\n    <span class=\"n\">m<\/span><span class=\"p\">,<\/span><span class=\"n\">n<\/span><span class=\"p\">,<\/span><span class=\"n\">nrhs<\/span><span class=\"p\">,<\/span><span class=\"n\">ldX<\/span><span class=\"p\">,<\/span><span class=\"n\">ldY<\/span><span class=\"p\">,<\/span><span class=\"n\">la_order<\/span> <span class=\"o\">=<\/span> <span class=\"n\">simple_args<\/span><span class=\"p\">(<\/span><span class=\"n\">X<\/span><span class=\"p\">,<\/span><span class=\"n\">Y<\/span><span class=\"p\">,<\/span><span class=\"n\">trans<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"n\">X_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">Y_p<\/span> <span class=\"o\">=<\/span> <span class=\"n\">get_dptr<\/span><span class=\"p\">(<\/span><span class=\"n\">X<\/span><span class=\"p\">),<\/span> <span class=\"n\">get_dptr<\/span><span class=\"p\">(<\/span><span class=\"n\">Y<\/span><span class=\"p\">)<\/span>\r\n\r\n    <span class=\"n\">TN<\/span> <span class=\"o\">=<\/span> <span class=\"s\">&quot;N&quot;<\/span> <span class=\"k\">if<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">trans<\/span> <span class=\"k\">else<\/span> <span class=\"s\">&quot;T&quot;<\/span>\r\n    <span class=\"n\">info<\/span> <span class=\"o\">=<\/span> <span class=\"n\">lap<\/span><span class=\"o\">.<\/span><span class=\"n\">LAPACKE_dgels<\/span><span class=\"p\">(<\/span><span class=\"n\">la_order<\/span><span class=\"p\">,<\/span> <span class=\"n\">TN<\/span><span class=\"p\">,<\/span> <span class=\"n\">m<\/span><span class=\"p\">,<\/span><span class=\"n\">n<\/span><span class=\"p\">,<\/span><span class=\"n\">nrhs<\/span><span class=\"p\">,<\/span>\r\n                             <span class=\"n\">X_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">ldX<\/span><span class=\"p\">,<\/span> <span class=\"n\">Y_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">ldY<\/span><span class=\"p\">)<\/span>    \r\n\r\n    <span class=\"n\">beta_shape<\/span> <span class=\"o\">=<\/span> <span class=\"n\">n<\/span> <span class=\"k\">if<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">trans<\/span> <span class=\"k\">else<\/span> <span class=\"n\">m<\/span>\r\n    <span class=\"k\">return<\/span> <span class=\"n\">Y<\/span><span class=\"p\">[:<\/span><span class=\"n\">beta_shape<\/span><span class=\"p\">]<\/span>\r\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<h3 id=\"manual-implementation-of-dgelsd-for-c-order-without-copies\">Manual Implementation of <code>dgelsd<\/code> for C-order without Copies<\/h3>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<p>However, for <span class=\"math inline\">\\(SVD\\)<\/span>, things are a bit more tricky. I based this off of <code>dgelsd<\/code> (the more &quot;modern&quot;, slightly faster, slightly more memory hungry) <span class=\"math inline\">\\(SVD\\)<\/span> least squares solver. Since the outer <code>dgelsd<\/code> call doesn&#8217;t know about solving a transposed problem, we have to <em>do it by hand<\/em>.<\/p>\n<p>Here&#8217;s a small aside. The <em>entire<\/em> LAPACKE wrapper for LAPACK could adopt the techniques I&#8217;m using in this post and <em>in many cases<\/em> (if not all?), could remove unnecessary copies. The problem is that copying and transposing gives a <em>direct<\/em> way to use the preexisting implementation. The techniques I use here require <em>rewriting<\/em> a (mathematically) equivalent implementation. LAPACK is unweidly (from a software engineering perspective), as is. It&#8217;s an exact case study for the labor savings of OOP (the tradeoff for performance is the main competingn concern). Adding another set of implementations is sure to causes headaches.<\/p>\n<p>The basic algorithm of <code>dgelsd<\/code> given the regression problem <span class=\"math inline\">\\(X \\beta = Y\\)<\/span> is:<\/p>\n<ol style=\"list-style-type: decimal\">\n<li>Decompose X to two orthogonal matrices and an (upper) bidiagonal: <span class=\"math inline\">\\(X \\rightarrow QBP\\)<\/span><\/li>\n<li>Rotate <span class=\"math inline\">\\(Y\\)<\/span> with <span class=\"math inline\">\\(Q^T\\)<\/span> (this is done inplace, so it updates <span class=\"math inline\">\\(Y \\leftarrow Q^T Y\\)<\/span>)<\/li>\n<li>Solve <span class=\"math inline\">\\(B\\beta_{tmp} = Q^T Y\\)<\/span> (written in original terms, don&#8217;t rotate <span class=\"math inline\">\\(Y\\)<\/span> twice). Note, this process gives us both the <span class=\"math inline\">\\(\\beta_{tmp}\\)<\/span> <em>and<\/em> the singular values of <span class=\"math inline\">\\(X\\)<\/span>.<\/li>\n<li>Rotate <span class=\"math inline\">\\(\\beta_{tmp}\\)<\/span> with <span class=\"math inline\">\\(P\\)<\/span>: <span class=\"math inline\">\\(\\beta \\leftarrow P\\beta_{tmp}\\)<\/span><\/li>\n<\/ol>\n<p>These steps are performed with these Fortran LAPACK calls:<\/p>\n<ol style=\"list-style-type: decimal\">\n<li><code>dgebrd<\/code><\/li>\n<li><code>dormbr<\/code><\/li>\n<li><code>dlalsd<\/code><\/li>\n<li><code>dormbr<\/code><\/li>\n<\/ol>\n<p>The combination of <code>dgebrd<\/code> and <code>dlalsd<\/code> performs an &quot;applied&quot; <span class=\"math inline\">\\(SVD\\)<\/span>. <code>dgebrd<\/code> actually computes <span class=\"math inline\">\\(Q\\)<\/span> and <span class=\"math inline\">\\(P^T\\)<\/span> (which is status quo in many numerical systems &#8212; probably because they either call LAPACK or want to be compatible).<\/p>\n<p>One other step in <code>dgelsd<\/code> is dealing with the case when there are many more rows than columns. Actually, <code>dgelsd<\/code> handles under- and over-determined systems differently, but we&#8217;re assuming (and simplifying) to consider that we are either (1) square or (2) tall-and-skinny (over-determined). With many more rows than columsn, <code>dgelsd<\/code> first performs a <span class=\"math inline\">\\(QR\\)<\/span> decomposition (using <code>dgeqrf<\/code>) on <span class=\"math inline\">\\(X\\)<\/span> and then proceeds as above using <span class=\"math inline\">\\(R\\)<\/span> in place of <span class=\"math inline\">\\(X\\)<\/span> and <span class=\"math inline\">\\(Q_{QR}^T Y\\)<\/span> for <span class=\"math inline\">\\(Y\\)<\/span> (the rotation is applied using <code>dormqr<\/code>).<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<p>That is all the &quot;easy&quot; case. We still have talked about what it means to do <code>dgelsd<\/code> in a transposed sense. Remember, <code>dgels<\/code> had an explicit <code>T<\/code>\/<code>N<\/code> flag that could control whether to transpose the system or not. Without futher ado, here&#8217;s the mathematical outline of steps:<\/p>\n<ol style=\"list-style-type: decimal\">\n<li><span class=\"math inline\">\\(X^T \\rightarrow (QBP)^T = P^TB^TQ^T\\)<\/span> (as above with <span class=\"math inline\">\\(Q \\leftrightarrow P\\)<\/span>; <span class=\"math inline\">\\(B\\)<\/span> was upper bidiagonal, <span class=\"math inline\">\\(B^T\\)<\/span> should be lower bidiagonal.<\/li>\n<li>Rotate <span class=\"math inline\">\\(Y\\)<\/span> with <span class=\"math inline\">\\(P^T\\)<\/span>: <span class=\"math inline\">\\(Y \\leftarrow P^TY\\)<\/span><\/li>\n<li>Solve <span class=\"math inline\">\\(B^T \\beta_{tmp} = P^TY\\)<\/span> giving <span class=\"math inline\">\\(\\beta_{tmp}\\)<\/span> and the singular values<\/li>\n<li>Rotate <span class=\"math inline\">\\(\\beta_{tmp}\\)<\/span> with <span class=\"math inline\">\\(Q\\)<\/span>: <span class=\"math inline\">\\(\\beta \\leftarrow Q\\beta_{tmp}\\)<\/span><\/li>\n<\/ol>\n<p>The analogue (tranpose) of the <span class=\"math inline\">\\(QR\\)<\/span> step for the many rows case, is to perform an <span class=\"math inline\">\\(LQ\\)<\/span> decomposition (the decomposition is done with <code>dgelqf<\/code> and the rotation is applied with <code>dormlq<\/code>). That is: <span class=\"math inline\">\\(QR(X)\\)<\/span> and <span class=\"math inline\">\\(LQ(X.T)\\)<\/span> give equivalent <span class=\"math inline\">\\(Q\\)<\/span> values.<\/p>\n<p>Getting all of this encoded properly was a royal pain in the tookis. The row-\/col-major ordering, the transposed problem, the transposes (or not) on the orthogonal matrices, the upper-\/lower- bidiagonalization (which is only computed as an upper because we have more rows than columns), and the differences in how LAPACK&#8217;s <span class=\"math inline\">\\(QR\\)<\/span> and <span class=\"math inline\">\\(LQ\\)<\/span> store their results made me feel like I was caught in an Escher painting.<\/p>\n<p>In additional to encoding the mathematics (properly), it turned out that <code>dlalsd<\/code> is not wrapped by the current version of LAPACKE. So, I had to write a small wrapper for it.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<h3 id=\"dlalsd-aside\">dlalsd Aside<\/h3>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<p>To make use of <code>dlalsd<\/code>, I wrote a tiny C library. Then, I exposed it in Python using the <a href=\"http:\/\/drsfenner.org\/blog\/2015\/12\/into-the-weeds-i-lapack-lapacke-and-three-paths\/\"><code>cffi<\/code> technique<\/a>. I then gave it a Python function name of <code>my_dlalsd<\/code>. My wrapper is to the LAPACK (FORTRAN) function, so it doesn&#8217;t have the C-ORDER &quot;capabilities&quot; (automatic copy and transpose) of other LAPACKE routines.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<p>Here&#8217;s a quick look at the .h, .c, and Makefile and the Python helper. The <code>dlalsd_<\/code> declaration is necesssary because it is defined externally (in <code>libreflapack.so<\/code>).<\/p>\n<div class=\"sourceCode\">\n<pre class=\"sourceCode c\"><code class=\"sourceCode c\"><span class=\"co\">\/* mylapack.h *\/<\/span>\r\n<span class=\"dt\">int<\/span> MY_dlalsd_work(<span class=\"dt\">char<\/span> UPLO, <span class=\"dt\">int<\/span> SMLSIZ, <span class=\"dt\">int<\/span> N, <span class=\"dt\">int<\/span> NRHS, \r\n                   <span class=\"dt\">double<\/span> *D, <span class=\"dt\">double<\/span> *E, <span class=\"dt\">double<\/span> *B,<span class=\"dt\">int<\/span> LDB,   \r\n                   <span class=\"dt\">double<\/span> RCOND, <span class=\"dt\">int<\/span> *rank, <span class=\"dt\">double<\/span> *work, <span class=\"dt\">int<\/span> *iwork);   \r\n\r\n<span class=\"co\">\/* mylapack.c *\/<\/span>\r\n<span class=\"ot\">#include &lt;stdio.h&gt;<\/span>\r\n<span class=\"dt\">void<\/span> dlalsd_(<span class=\"dt\">char<\/span> *UPLO, <span class=\"dt\">int<\/span> *SMLSIZ, <span class=\"dt\">int<\/span> *N, <span class=\"dt\">int<\/span> *NRHS,\r\n             <span class=\"dt\">double<\/span> *D, <span class=\"dt\">double<\/span> *E, <span class=\"dt\">double<\/span> *B,<span class=\"dt\">int<\/span> *LDB,\r\n             <span class=\"dt\">double<\/span> *RCOND, <span class=\"dt\">int<\/span> *rank, <span class=\"dt\">double<\/span> *work, <span class=\"dt\">int<\/span> *iwork, <span class=\"dt\">int<\/span> *info);\r\n\r\n<span class=\"dt\">int<\/span> MY_dlalsd_work(<span class=\"dt\">char<\/span> UPLO, <span class=\"dt\">int<\/span> SMLSIZ, <span class=\"dt\">int<\/span> N, <span class=\"dt\">int<\/span> NRHS,\r\n                   <span class=\"dt\">double<\/span> *D, <span class=\"dt\">double<\/span> *E, <span class=\"dt\">double<\/span> *B,<span class=\"dt\">int<\/span> LDB,\r\n                   <span class=\"dt\">double<\/span> RCOND, <span class=\"dt\">int<\/span> *rank, <span class=\"dt\">double<\/span> *work, <span class=\"dt\">int<\/span> *iwork){\r\n  <span class=\"dt\">int<\/span> info = <span class=\"dv\">0<\/span>;\r\n  dlalsd_(&amp;UPLO, &amp;SMLSIZ, &amp;N, &amp;NRHS, D, E, B, &amp;LDB, &amp;RCOND, rank, work, iwork, &amp;info);\r\n  <span class=\"kw\">if<\/span> (info &lt; <span class=\"dv\">0<\/span>)\r\n    info = info - <span class=\"dv\">1<\/span>;\r\n\r\n  <span class=\"kw\">return<\/span> info;\r\n}<\/code><\/pre>\n<\/div>\n<p>This Makefile worked on my Linux box with gcc.<\/p>\n<div class=\"sourceCode\">\n<pre class=\"sourceCode bash\"><code class=\"sourceCode bash\"><span class=\"co\"># Makefile<\/span>\r\n<span class=\"kw\">all<\/span>:\r\n    <span class=\"kw\">gcc<\/span> -shared -fPIC -o libmylapack.so mylapack.c -lreflapack<\/code><\/pre>\n<\/div>\n<p>And a little Python shim.<\/p>\n<div class=\"sourceCode\">\n<pre class=\"sourceCode python\"><code class=\"sourceCode python\"><span class=\"im\">from<\/span> cffi <span class=\"im\">import<\/span> FFI\r\nffi <span class=\"op\">=<\/span> FFI()\r\n<span class=\"cf\">with<\/span> <span class=\"bu\">open<\/span>(<span class=\"st\">&quot;.\/mylapack.h&quot;<\/span>) <span class=\"im\">as<\/span> header_file:\r\n    ffi.cdef(header_file.read())\r\nmylapack <span class=\"op\">=<\/span> ffi.dlopen(<span class=\"st\">&quot;.\/libmylapack.so&quot;<\/span>)\r\nmy_dlalsd <span class=\"op\">=<\/span> mylapack.MY_dlalsd_work<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<h3 id=\"the-code\">The Code<\/h3>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing code_cell rendered\">\n<div class=\"input\">\n<div class=\"prompt input_prompt\">In&nbsp;[3]:<\/div>\n<div class=\"inner_cell\">\n<div class=\"input_area\">\n<div class=\" highlight hl-ipython2\">\n<pre><span class=\"k\">def<\/span> <span class=\"nf\">sls_svd_via_manual_dgelsd<\/span><span class=\"p\">(<\/span><span class=\"n\">X<\/span><span class=\"p\">,<\/span><span class=\"n\">Y<\/span><span class=\"p\">,<\/span><span class=\"n\">trans<\/span><span class=\"p\">):<\/span>\r\n    <span class=\"sd\">&#39;&#39;&#39; we essentially reimplement dgelsd and add the capability of solving a transposed problem<\/span>\r\n<span class=\"sd\">        i.e., $X \\Beta = Y$ --or-- $X.T \\Beta = Y$ &#39;&#39;&#39;<\/span>\r\n    <span class=\"k\">assert<\/span> <span class=\"n\">X<\/span><span class=\"o\">.<\/span><span class=\"n\">flags<\/span><span class=\"o\">.<\/span><span class=\"n\">f_contiguous<\/span> <span class=\"ow\">or<\/span> <span class=\"n\">trans<\/span>    \r\n    <span class=\"n\">common_args<\/span><span class=\"p\">,<\/span> <span class=\"n\">arrays<\/span><span class=\"p\">,<\/span> <span class=\"n\">pointers<\/span> <span class=\"o\">=<\/span> <span class=\"n\">make_structures_dgelsd<\/span><span class=\"p\">(<\/span><span class=\"n\">X<\/span><span class=\"p\">,<\/span><span class=\"n\">Y<\/span><span class=\"p\">,<\/span><span class=\"n\">trans<\/span><span class=\"o\">=<\/span><span class=\"n\">trans<\/span><span class=\"p\">)<\/span>\r\n\r\n    <span class=\"n\">m<\/span><span class=\"p\">,<\/span><span class=\"n\">n<\/span><span class=\"p\">,<\/span><span class=\"n\">nrhs<\/span><span class=\"p\">,<\/span><span class=\"n\">ldX<\/span><span class=\"p\">,<\/span><span class=\"n\">ldY<\/span><span class=\"p\">,<\/span><span class=\"n\">la_order<\/span>                       <span class=\"o\">=<\/span> <span class=\"n\">common_args<\/span>\r\n    <span class=\"n\">singular_values<\/span><span class=\"p\">,<\/span> <span class=\"n\">E<\/span><span class=\"p\">,<\/span> <span class=\"n\">Q<\/span><span class=\"p\">,<\/span> <span class=\"n\">P<\/span><span class=\"p\">,<\/span> <span class=\"n\">rank<\/span><span class=\"p\">,<\/span> <span class=\"n\">M<\/span><span class=\"p\">,<\/span> <span class=\"n\">I<\/span>            <span class=\"o\">=<\/span> <span class=\"n\">arrays<\/span>\r\n    <span class=\"n\">X_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">Y_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">SV_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">E_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">Q_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">P_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">rank_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">M_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">I_p<\/span> <span class=\"o\">=<\/span> <span class=\"n\">pointers<\/span>\r\n\r\n    <span class=\"c\"># helpful names<\/span>\r\n    <span class=\"n\">minmn<\/span> <span class=\"o\">=<\/span> <span class=\"nb\">min<\/span><span class=\"p\">(<\/span><span class=\"n\">m<\/span><span class=\"p\">,<\/span><span class=\"n\">n<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"n\">real_rows<\/span><span class=\"p\">,<\/span> <span class=\"n\">real_cols<\/span> <span class=\"o\">=<\/span> <span class=\"p\">(<\/span><span class=\"n\">m<\/span><span class=\"p\">,<\/span><span class=\"n\">n<\/span><span class=\"p\">)<\/span> <span class=\"k\">if<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">trans<\/span> <span class=\"k\">else<\/span> <span class=\"p\">(<\/span><span class=\"n\">n<\/span><span class=\"p\">,<\/span><span class=\"n\">m<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"n\">y_shape<\/span><span class=\"p\">,<\/span> <span class=\"n\">beta_shape<\/span> <span class=\"o\">=<\/span> <span class=\"n\">real_rows<\/span><span class=\"p\">,<\/span> <span class=\"n\">real_cols<\/span>\r\n\r\n    <span class=\"n\">UL_arg<\/span> <span class=\"o\">=<\/span> <span class=\"s\">&#39;U&#39;<\/span> <span class=\"c\"># without preprocessing, both trans={T,F} need &#39;U&#39;<\/span>\r\n\r\n    <span class=\"c\"># cross over for pre-QRing is defined at:<\/span>\r\n    <span class=\"c\"># tsiee.f line 576 ... thresh = min(m,n) * 1.6<\/span>\r\n    <span class=\"n\">lotsaRows<\/span> <span class=\"o\">=<\/span> <span class=\"n\">real_rows<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"n\">real_cols<\/span> <span class=\"o\">*<\/span> <span class=\"mf\">1.6<\/span>\r\n    <span class=\"k\">if<\/span> <span class=\"n\">lotsaRows<\/span><span class=\"p\">:<\/span>\r\n        <span class=\"k\">if<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">trans<\/span><span class=\"p\">:<\/span>\r\n            <span class=\"n\">decomp<\/span><span class=\"p\">,<\/span> <span class=\"n\">mult<\/span><span class=\"p\">,<\/span> <span class=\"n\">TN<\/span> <span class=\"o\">=<\/span> <span class=\"n\">lap<\/span><span class=\"o\">.<\/span><span class=\"n\">LAPACKE_dgeqrf_work<\/span><span class=\"p\">,<\/span> <span class=\"n\">lap<\/span><span class=\"o\">.<\/span><span class=\"n\">LAPACKE_dormqr_work<\/span><span class=\"p\">,<\/span> <span class=\"s\">&#39;T&#39;<\/span>\r\n        <span class=\"k\">else<\/span><span class=\"p\">:<\/span>\r\n            <span class=\"n\">decomp<\/span><span class=\"p\">,<\/span> <span class=\"n\">mult<\/span><span class=\"p\">,<\/span> <span class=\"n\">TN<\/span> <span class=\"o\">=<\/span> <span class=\"n\">lap<\/span><span class=\"o\">.<\/span><span class=\"n\">LAPACKE_dgelqf_work<\/span><span class=\"p\">,<\/span> <span class=\"n\">lap<\/span><span class=\"o\">.<\/span><span class=\"n\">LAPACKE_dormlq_work<\/span><span class=\"p\">,<\/span> <span class=\"s\">&#39;N&#39;<\/span>\r\n            <span class=\"n\">UL_arg<\/span> <span class=\"o\">=<\/span> <span class=\"s\">&#39;L&#39;<\/span> <span class=\"c\"># B\/E need to be used to define a lower bi-di<\/span>\r\n\r\n        <span class=\"n\">info<\/span> <span class=\"o\">=<\/span> <span class=\"n\">decomp<\/span><span class=\"p\">(<\/span><span class=\"n\">la_order<\/span><span class=\"p\">,<\/span> <span class=\"n\">m<\/span><span class=\"p\">,<\/span> <span class=\"n\">n<\/span><span class=\"p\">,<\/span> <span class=\"n\">X_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">ldX<\/span><span class=\"p\">,<\/span> <span class=\"n\">E_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">M_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">M<\/span><span class=\"o\">.<\/span><span class=\"n\">size<\/span><span class=\"p\">)<\/span>\r\n        <span class=\"k\">assert<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">info<\/span><span class=\"p\">,<\/span> <span class=\"s\">&quot;<\/span><span class=\"si\">%s<\/span><span class=\"s\">:<\/span><span class=\"si\">%s<\/span><span class=\"s\">&quot;<\/span> <span class=\"o\">%<\/span> <span class=\"p\">(<\/span><span class=\"n\">decomp<\/span><span class=\"o\">.<\/span><span class=\"n\">__name__<\/span><span class=\"p\">,<\/span> <span class=\"n\">info<\/span><span class=\"p\">)<\/span>\r\n\r\n        <span class=\"c\"># this can be done at the end (so says dgelsd.f) <\/span>\r\n        <span class=\"n\">info<\/span> <span class=\"o\">=<\/span> <span class=\"n\">mult<\/span><span class=\"p\">(<\/span><span class=\"n\">la_order<\/span><span class=\"p\">,<\/span> <span class=\"s\">&#39;L&#39;<\/span><span class=\"p\">,<\/span> <span class=\"n\">TN<\/span><span class=\"p\">,<\/span> <span class=\"n\">y_shape<\/span><span class=\"p\">,<\/span> <span class=\"n\">nrhs<\/span><span class=\"p\">,<\/span> <span class=\"n\">real_cols<\/span><span class=\"p\">,<\/span> <span class=\"n\">X_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">ldX<\/span><span class=\"p\">,<\/span> <span class=\"n\">E_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">Y_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">ldY<\/span><span class=\"p\">,<\/span> <span class=\"n\">M_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">M<\/span><span class=\"o\">.<\/span><span class=\"n\">size<\/span><span class=\"p\">)<\/span>\r\n        <span class=\"k\">assert<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">info<\/span><span class=\"p\">,<\/span> <span class=\"s\">&quot;<\/span><span class=\"si\">%s<\/span><span class=\"s\">:<\/span><span class=\"si\">%s<\/span><span class=\"s\">&quot;<\/span> <span class=\"o\">%<\/span> <span class=\"p\">(<\/span><span class=\"n\">decomp<\/span><span class=\"o\">.<\/span><span class=\"n\">__name__<\/span><span class=\"p\">,<\/span> <span class=\"n\">info<\/span><span class=\"p\">)<\/span>\r\n            \r\n        <span class=\"n\">y_shape<\/span> <span class=\"o\">=<\/span> <span class=\"n\">beta_shape<\/span> <span class=\"o\">=<\/span> <span class=\"n\">n<\/span> <span class=\"o\">=<\/span> <span class=\"n\">m<\/span> <span class=\"o\">=<\/span> <span class=\"n\">minmn<\/span>\r\n        <span class=\"n\">X<\/span><span class=\"p\">[<\/span><span class=\"n\">np<\/span><span class=\"o\">.<\/span><span class=\"n\">tril_indices<\/span><span class=\"p\">(<\/span><span class=\"n\">minmn<\/span><span class=\"p\">,<\/span> <span class=\"o\">-<\/span><span class=\"mi\">1<\/span><span class=\"p\">)]<\/span> <span class=\"o\">=<\/span> <span class=\"mf\">0.0<\/span>\r\n\r\n    <span class=\"c\"># print &quot;calling dgebrd&quot;<\/span>\r\n    <span class=\"k\">assert<\/span> <span class=\"n\">M<\/span><span class=\"o\">.<\/span><span class=\"n\">size<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"nb\">max<\/span><span class=\"p\">(<\/span><span class=\"n\">m<\/span><span class=\"p\">,<\/span><span class=\"n\">n<\/span><span class=\"p\">)<\/span> <span class=\"c\"># minimal workspace<\/span>\r\n    <span class=\"n\">info<\/span> <span class=\"o\">=<\/span> <span class=\"n\">lap<\/span><span class=\"o\">.<\/span><span class=\"n\">LAPACKE_dgebrd_work<\/span><span class=\"p\">(<\/span><span class=\"n\">la_order<\/span><span class=\"p\">,<\/span> <span class=\"n\">m<\/span><span class=\"p\">,<\/span> <span class=\"n\">n<\/span><span class=\"p\">,<\/span> <span class=\"n\">X_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">ldX<\/span><span class=\"p\">,<\/span> <span class=\"n\">SV_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">E_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">Q_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">P_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">M_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">M<\/span><span class=\"o\">.<\/span><span class=\"n\">size<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"k\">assert<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">info<\/span><span class=\"p\">,<\/span> <span class=\"s\">&quot;dgebrd bad arg <\/span><span class=\"si\">%d<\/span><span class=\"s\">&quot;<\/span> <span class=\"o\">%<\/span> <span class=\"n\">info<\/span>\r\n\r\n    <span class=\"n\">pq<\/span><span class=\"p\">,<\/span> <span class=\"n\">PQ_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">real_k<\/span> <span class=\"o\">=<\/span> <span class=\"p\">(<\/span><span class=\"s\">&#39;Q&#39;<\/span><span class=\"p\">,<\/span> <span class=\"n\">Q_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">n<\/span><span class=\"p\">)<\/span>  <span class=\"k\">if<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">trans<\/span> <span class=\"k\">else<\/span> <span class=\"p\">(<\/span><span class=\"s\">&#39;P&#39;<\/span><span class=\"p\">,<\/span> <span class=\"n\">P_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">m<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"c\"># print &quot;calling dormbr (I)&quot;  <\/span>\r\n    <span class=\"n\">info<\/span> <span class=\"o\">=<\/span> <span class=\"n\">lap<\/span><span class=\"o\">.<\/span><span class=\"n\">LAPACKE_dormbr_work<\/span><span class=\"p\">(<\/span><span class=\"n\">la_order<\/span><span class=\"p\">,<\/span> <span class=\"n\">pq<\/span><span class=\"p\">,<\/span> <span class=\"s\">&#39;L&#39;<\/span><span class=\"p\">,<\/span> <span class=\"s\">&#39;T&#39;<\/span><span class=\"p\">,<\/span> <span class=\"n\">y_shape<\/span><span class=\"p\">,<\/span> <span class=\"n\">nrhs<\/span><span class=\"p\">,<\/span> <span class=\"n\">real_cols<\/span><span class=\"p\">,<\/span>\r\n                                   <span class=\"n\">X_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">ldX<\/span><span class=\"p\">,<\/span> <span class=\"n\">PQ_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">Y_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">ldY<\/span><span class=\"p\">,<\/span> <span class=\"n\">M_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">M<\/span><span class=\"o\">.<\/span><span class=\"n\">size<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"k\">assert<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">info<\/span><span class=\"p\">,<\/span> <span class=\"s\">&quot;dormbr (I) bad arg <\/span><span class=\"si\">%d<\/span><span class=\"s\">&quot;<\/span> <span class=\"o\">%<\/span> <span class=\"n\">info<\/span>\r\n    \r\n    <span class=\"c\"># print &quot;calling dlalsd&quot;<\/span>\r\n    <span class=\"c\"># FIXME: this wraps a direct call to LAPACK (broken for C-ORDER)<\/span>\r\n    <span class=\"c\"># 25 is &quot;small size&quot; for this dlalsd<\/span>\r\n    <span class=\"c\"># -1 says to use machine eps for rcond parameter (det. small singular values)<\/span>\r\n    <span class=\"n\">info<\/span> <span class=\"o\">=<\/span> <span class=\"n\">my_dlalsd<\/span><span class=\"p\">(<\/span><span class=\"n\">UL_arg<\/span><span class=\"p\">,<\/span> <span class=\"mi\">25<\/span><span class=\"p\">,<\/span> <span class=\"n\">minmn<\/span><span class=\"p\">,<\/span> <span class=\"n\">nrhs<\/span><span class=\"p\">,<\/span> <span class=\"n\">SV_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">E_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">Y_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">ldY<\/span><span class=\"p\">,<\/span> <span class=\"o\">-<\/span><span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"n\">rank_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">M_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">I_p<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"k\">assert<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">info<\/span><span class=\"p\">,<\/span> <span class=\"s\">&quot;dlalsd bad arg <\/span><span class=\"si\">%d<\/span><span class=\"s\">&quot;<\/span> <span class=\"o\">%<\/span> <span class=\"n\">info<\/span>\r\n    \r\n    <span class=\"n\">pq<\/span><span class=\"p\">,<\/span> <span class=\"n\">PQ_p<\/span> <span class=\"o\">=<\/span> <span class=\"p\">(<\/span><span class=\"s\">&#39;P&#39;<\/span><span class=\"p\">,<\/span> <span class=\"n\">P_p<\/span><span class=\"p\">)<\/span> <span class=\"k\">if<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">trans<\/span> <span class=\"k\">else<\/span> <span class=\"p\">(<\/span><span class=\"s\">&#39;Q&#39;<\/span><span class=\"p\">,<\/span> <span class=\"n\">Q_p<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"c\"># print &quot;calling dormbr (II)&quot;<\/span>\r\n    <span class=\"n\">info<\/span> <span class=\"o\">=<\/span> <span class=\"n\">lap<\/span><span class=\"o\">.<\/span><span class=\"n\">LAPACKE_dormbr_work<\/span><span class=\"p\">(<\/span><span class=\"n\">la_order<\/span><span class=\"p\">,<\/span> <span class=\"n\">pq<\/span><span class=\"p\">,<\/span> <span class=\"s\">&#39;L&#39;<\/span><span class=\"p\">,<\/span> <span class=\"s\">&#39;N&#39;<\/span><span class=\"p\">,<\/span> <span class=\"n\">beta_shape<\/span><span class=\"p\">,<\/span> <span class=\"n\">nrhs<\/span><span class=\"p\">,<\/span> <span class=\"n\">n<\/span><span class=\"p\">,<\/span>\r\n                                   <span class=\"n\">X_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">ldX<\/span><span class=\"p\">,<\/span> <span class=\"n\">PQ_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">Y_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">ldY<\/span><span class=\"p\">,<\/span> <span class=\"n\">M_p<\/span><span class=\"p\">,<\/span> <span class=\"n\">M<\/span><span class=\"o\">.<\/span><span class=\"n\">size<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"k\">assert<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">info<\/span><span class=\"p\">,<\/span> <span class=\"s\">&quot;dormbr (II) bad arg <\/span><span class=\"si\">%d<\/span><span class=\"s\">&quot;<\/span> <span class=\"o\">%<\/span> <span class=\"n\">info<\/span>\r\n\r\n    <span class=\"k\">return<\/span> <span class=\"n\">Y<\/span><span class=\"p\">[:<\/span><span class=\"n\">beta_shape<\/span><span class=\"p\">],<\/span> <span class=\"n\">singular_values<\/span>\r\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<h3 id=\"testing\">Testing<\/h3>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<p>There were more than enough corner cases (and missteps) while I was coding to actually want test cases so I didn&#8217;t &quot;fix&quot; problems with regressions (the software engineering kind) in other parts of the code. So, some <code>unittests<\/code> to the rescue:<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing code_cell rendered\">\n<div class=\"input\">\n<div class=\"prompt input_prompt\">In&nbsp;[4]:<\/div>\n<div class=\"inner_cell\">\n<div class=\"input_area\">\n<div class=\" highlight hl-ipython2\">\n<pre><span class=\"o\">!<\/span>python test_dgelsd.py\r\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"output_wrapper\">\n<div class=\"output\">\n<div class=\"output_area\">\n<div class=\"prompt\"><\/div>\n<div class=\"output_subarea output_stream output_stdout output_text\">\n<pre>test_heavy_pure_fortran_1 (__main__.TestManual_dgelsd) ... ok\r\ntest_heavy_pure_fortran_2 (__main__.TestManual_dgelsd) ... ok\r\ntest_heavy_tranposed_corder_1 (__main__.TestManual_dgelsd) ... ok\r\ntest_heavy_tranposed_corder_2 (__main__.TestManual_dgelsd) ... ok\r\ntest_medium_pure_fortran_1 (__main__.TestManual_dgelsd) ... ok\r\ntest_medium_pure_fortran_2 (__main__.TestManual_dgelsd) ... ok\r\ntest_medium_tranposed_corder_1 (__main__.TestManual_dgelsd) ... ok\r\ntest_medium_tranposed_corder_2 (__main__.TestManual_dgelsd) ... ok\r\ntest_simple_pure_fortran (__main__.TestManual_dgelsd) ... ok\r\ntest_simple_transposed_corder (__main__.TestManual_dgelsd) ... ok\r\n\r\n----------------------------------------------------------------------\r\nRan 10 tests in 40.326s\r\n\r\nOK\r\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<h3 id=\"same-results\">Same Results?<\/h3>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<p>So, let&#8217;s run each of these routines with row- and column-major data and verify we get the same output.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing code_cell rendered\">\n<div class=\"input\">\n<div class=\"prompt input_prompt\">In&nbsp;[5]:<\/div>\n<div class=\"inner_cell\">\n<div class=\"input_area\">\n<div class=\" highlight hl-ipython2\">\n<pre><span class=\"c\">#<\/span>\r\n<span class=\"c\"># we have to copy the data so we can use the same data in the next call<\/span>\r\n<span class=\"c\"># lapack\/e overwrite its inputs with the results (decomps and betas)<\/span>\r\n<span class=\"c\">#<\/span>\r\n<span class=\"k\">def<\/span> <span class=\"nf\">copy_and_do<\/span><span class=\"p\">(<\/span><span class=\"n\">func<\/span><span class=\"p\">,<\/span> <span class=\"n\">X<\/span><span class=\"p\">,<\/span> <span class=\"n\">Y<\/span><span class=\"p\">,<\/span> <span class=\"n\">trans<\/span><span class=\"p\">,<\/span> <span class=\"n\">order<\/span><span class=\"p\">):<\/span>\r\n    <span class=\"n\">_X<\/span><span class=\"p\">,<\/span><span class=\"n\">_Y<\/span> <span class=\"o\">=<\/span> <span class=\"n\">np<\/span><span class=\"o\">.<\/span><span class=\"n\">copy<\/span><span class=\"p\">(<\/span><span class=\"n\">X<\/span><span class=\"p\">,<\/span><span class=\"n\">order<\/span><span class=\"o\">=<\/span><span class=\"n\">order<\/span><span class=\"p\">),<\/span> <span class=\"n\">np<\/span><span class=\"o\">.<\/span><span class=\"n\">copy<\/span><span class=\"p\">(<\/span><span class=\"n\">Y<\/span><span class=\"p\">,<\/span><span class=\"n\">order<\/span><span class=\"o\">=<\/span><span class=\"n\">order<\/span><span class=\"p\">)<\/span>\r\n    <span class=\"k\">return<\/span> <span class=\"n\">func<\/span><span class=\"p\">(<\/span><span class=\"n\">_X<\/span><span class=\"p\">,<\/span><span class=\"n\">_Y<\/span><span class=\"p\">,<\/span><span class=\"n\">trans<\/span><span class=\"p\">)<\/span>\r\n\r\n<span class=\"n\">n_obs<\/span><span class=\"p\">,<\/span> <span class=\"n\">n_ftrs<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">2<\/span><span class=\"o\">**<\/span><span class=\"mi\">16<\/span><span class=\"p\">,<\/span> <span class=\"mi\">2<\/span><span class=\"o\">**<\/span><span class=\"mi\">10<\/span>\r\n<span class=\"c\">#n_obs, n_ftrs = 10, 2<\/span>\r\n<span class=\"c\">#n_obs, n_ftrs = 1024, 50<\/span>\r\n\r\n<span class=\"c\"># X,Y = create_data2()<\/span>\r\n<span class=\"n\">X<\/span><span class=\"p\">,<\/span><span class=\"n\">Y<\/span> <span class=\"o\">=<\/span> <span class=\"n\">create_data1<\/span><span class=\"p\">(<\/span><span class=\"n\">n_obs<\/span><span class=\"p\">,<\/span> <span class=\"n\">n_ftrs<\/span><span class=\"p\">)<\/span>\r\n<span class=\"n\">betas_qr_f<\/span>  <span class=\"o\">=<\/span> <span class=\"n\">copy_and_do<\/span><span class=\"p\">(<\/span><span class=\"n\">sls_qr_via_dgels<\/span><span class=\"p\">,<\/span>          <span class=\"n\">X<\/span><span class=\"p\">,<\/span> <span class=\"n\">Y<\/span><span class=\"p\">,<\/span> <span class=\"bp\">False<\/span><span class=\"p\">,<\/span> <span class=\"s\">&quot;F&quot;<\/span><span class=\"p\">)<\/span>\r\n<span class=\"n\">betas_svd_f<\/span> <span class=\"o\">=<\/span> <span class=\"n\">copy_and_do<\/span><span class=\"p\">(<\/span><span class=\"n\">sls_svd_via_manual_dgelsd<\/span><span class=\"p\">,<\/span> <span class=\"n\">X<\/span><span class=\"p\">,<\/span> <span class=\"n\">Y<\/span><span class=\"p\">,<\/span> <span class=\"bp\">False<\/span><span class=\"p\">,<\/span> <span class=\"s\">&quot;F&quot;<\/span><span class=\"p\">)[<\/span><span class=\"mi\">0<\/span><span class=\"p\">]<\/span>\r\n\r\n<span class=\"n\">betas_qr_c<\/span>  <span class=\"o\">=<\/span> <span class=\"n\">copy_and_do<\/span><span class=\"p\">(<\/span><span class=\"n\">sls_qr_via_dgels<\/span><span class=\"p\">,<\/span>          <span class=\"n\">X<\/span><span class=\"p\">,<\/span> <span class=\"n\">Y<\/span><span class=\"p\">,<\/span> <span class=\"bp\">True<\/span><span class=\"p\">,<\/span> <span class=\"s\">&quot;C&quot;<\/span><span class=\"p\">)<\/span>\r\n<span class=\"n\">betas_svd_c<\/span> <span class=\"o\">=<\/span> <span class=\"n\">copy_and_do<\/span><span class=\"p\">(<\/span><span class=\"n\">sls_svd_via_manual_dgelsd<\/span><span class=\"p\">,<\/span> <span class=\"n\">X<\/span><span class=\"p\">,<\/span> <span class=\"n\">Y<\/span><span class=\"p\">,<\/span> <span class=\"bp\">True<\/span><span class=\"p\">,<\/span> <span class=\"s\">&quot;C&quot;<\/span><span class=\"p\">)[<\/span><span class=\"mi\">0<\/span><span class=\"p\">]<\/span>\r\n\r\n<span class=\"n\">betas_expected<\/span> <span class=\"o\">=<\/span> <span class=\"n\">np<\/span><span class=\"o\">.<\/span><span class=\"n\">linalg<\/span><span class=\"o\">.<\/span><span class=\"n\">lstsq<\/span><span class=\"p\">(<\/span><span class=\"n\">X<\/span><span class=\"p\">,<\/span><span class=\"n\">Y<\/span><span class=\"p\">)[<\/span><span class=\"mi\">0<\/span><span class=\"p\">]<\/span>\r\n\r\n<span class=\"c\"># betas = [betas_qr_f, betas_svd_f[0], betas_qr_c, betas_svd_c[0]]<\/span>\r\n<span class=\"n\">betas<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span><span class=\"n\">betas_expected<\/span><span class=\"p\">,<\/span> <span class=\"n\">betas_qr_f<\/span><span class=\"p\">,<\/span> <span class=\"n\">betas_qr_c<\/span><span class=\"p\">,<\/span> <span class=\"n\">betas_svd_f<\/span><span class=\"p\">,<\/span> <span class=\"n\">betas_svd_c<\/span><span class=\"p\">]<\/span>\r\n<span class=\"c\">#for b in betas:<\/span>\r\n<span class=\"c\">#    print b<\/span>\r\n<span class=\"k\">print<\/span> <span class=\"s\">&quot;All same betas?:&quot;<\/span><span class=\"p\">,<\/span> <span class=\"nb\">all<\/span><span class=\"p\">(<\/span><span class=\"n\">np<\/span><span class=\"o\">.<\/span><span class=\"n\">allclose<\/span><span class=\"p\">(<\/span><span class=\"n\">b1<\/span><span class=\"p\">,<\/span> <span class=\"n\">b2<\/span><span class=\"p\">)<\/span> <span class=\"k\">for<\/span> <span class=\"n\">b1<\/span> <span class=\"ow\">in<\/span> <span class=\"n\">betas<\/span><span class=\"p\">[:<\/span><span class=\"o\">-<\/span><span class=\"mi\">1<\/span><span class=\"p\">]<\/span> <span class=\"k\">for<\/span> <span class=\"n\">b2<\/span> <span class=\"ow\">in<\/span> <span class=\"n\">betas<\/span><span class=\"p\">[<\/span><span class=\"mi\">1<\/span><span class=\"p\">:])<\/span>\r\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"output_wrapper\">\n<div class=\"output\">\n<div class=\"output_area\">\n<div class=\"prompt\"><\/div>\n<div class=\"output_subarea output_stream output_stdout output_text\">\n<pre>All same betas?: True\r\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<h3 id=\"memory-usage\">Memory Usage<\/h3>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing code_cell rendered\">\n<div class=\"input\">\n<div class=\"prompt input_prompt\">In&nbsp;[6]:<\/div>\n<div class=\"inner_cell\">\n<div class=\"input_area\">\n<div class=\" highlight hl-ipython2\">\n<pre><span class=\"n\">n_obs<\/span><span class=\"p\">,<\/span> <span class=\"n\">n_ftrs<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">2<\/span><span class=\"o\">**<\/span><span class=\"mi\">16<\/span><span class=\"p\">,<\/span> <span class=\"mi\">2<\/span><span class=\"o\">**<\/span><span class=\"mi\">10<\/span>\r\n<span class=\"n\">X<\/span><span class=\"p\">,<\/span><span class=\"n\">Y<\/span> <span class=\"o\">=<\/span> <span class=\"n\">create_data1<\/span><span class=\"p\">(<\/span><span class=\"n\">n_obs<\/span><span class=\"p\">,<\/span> <span class=\"n\">n_ftrs<\/span><span class=\"p\">,<\/span> <span class=\"s\">&quot;C&quot;<\/span><span class=\"p\">)<\/span>\r\n<span class=\"c\"># %memit sls_qr_via_dgels(X,Y,True)           # peak memory: 600.68 MiB, increment: 3.19 MiB<\/span>\r\n<span class=\"c\"># %memit sls_svd_via_manual_dgelsd(X,Y,True)  # peak memory: 584.77 MiB, increment: 20.05 MiB<\/span>\r\n<span class=\"c\"># for comparison:<\/span>\r\n<span class=\"c\"># %memit np.linalg.lstsq(X,Y)                 # peak memory: 1082.57 MiB, increment: 517.70 MiB<\/span>\r\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<p>And now, it&#8217;s time for a big happy dance with music: we avoided copies <em>AND<\/em> we stayed in row-major (C-land) order!<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<h3 id=\"timing\">Timing<\/h3>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing code_cell rendered\">\n<div class=\"input\">\n<div class=\"prompt input_prompt\">In&nbsp;[7]:<\/div>\n<div class=\"inner_cell\">\n<div class=\"input_area\">\n<div class=\" highlight hl-ipython2\">\n<pre><span class=\"n\">n_obs<\/span><span class=\"p\">,<\/span> <span class=\"n\">n_ftrs<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">2<\/span><span class=\"o\">**<\/span><span class=\"mi\">16<\/span><span class=\"p\">,<\/span> <span class=\"mi\">2<\/span><span class=\"o\">**<\/span><span class=\"mi\">10<\/span>\r\n<span class=\"n\">_X<\/span><span class=\"p\">,<\/span><span class=\"n\">_Y<\/span> <span class=\"o\">=<\/span> <span class=\"n\">create_data1<\/span><span class=\"p\">(<\/span><span class=\"n\">n_obs<\/span><span class=\"p\">,<\/span> <span class=\"n\">n_ftrs<\/span><span class=\"p\">,<\/span> <span class=\"s\">&quot;C&quot;<\/span><span class=\"p\">)<\/span>\r\n\r\n<span class=\"o\">%<\/span><span class=\"k\">timeit<\/span> np.linalg.lstsq(_X,_Y)\r\n\r\n<span class=\"c\"># forcing copies since all of these calls place the results in the input (overwrite our data)<\/span>\r\n<span class=\"n\">X<\/span><span class=\"p\">,<\/span><span class=\"n\">Y<\/span> <span class=\"o\">=<\/span> <span class=\"n\">np<\/span><span class=\"o\">.<\/span><span class=\"n\">copy<\/span><span class=\"p\">(<\/span><span class=\"n\">_X<\/span><span class=\"p\">),<\/span> <span class=\"n\">np<\/span><span class=\"o\">.<\/span><span class=\"n\">copy<\/span><span class=\"p\">(<\/span><span class=\"n\">_Y<\/span><span class=\"p\">)<\/span>\r\n<span class=\"o\">%<\/span><span class=\"k\">timeit<\/span> sls_qr_via_dgels(X,Y,True)\r\n\r\n<span class=\"n\">X<\/span><span class=\"p\">,<\/span><span class=\"n\">Y<\/span> <span class=\"o\">=<\/span> <span class=\"n\">np<\/span><span class=\"o\">.<\/span><span class=\"n\">copy<\/span><span class=\"p\">(<\/span><span class=\"n\">_X<\/span><span class=\"p\">),<\/span> <span class=\"n\">np<\/span><span class=\"o\">.<\/span><span class=\"n\">copy<\/span><span class=\"p\">(<\/span><span class=\"n\">_Y<\/span><span class=\"p\">)<\/span>\r\n<span class=\"o\">%<\/span><span class=\"k\">timeit<\/span> sls_svd_via_manual_dgelsd(X,Y,True)\r\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"output_wrapper\">\n<div class=\"output\">\n<div class=\"output_area\">\n<div class=\"prompt\"><\/div>\n<div class=\"output_subarea output_stream output_stdout output_text\">\n<pre>1 loops, best of 3: 8.72 s per loop\r\n1 loops, best of 3: 11.9 s per loop\r\n1 loops, best of 3: 11.7 s per loop\r\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing code_cell rendered\">\n<div class=\"input\">\n<div class=\"prompt input_prompt\">In&nbsp;[8]:<\/div>\n<div class=\"inner_cell\">\n<div class=\"input_area\">\n<div class=\" highlight hl-ipython2\">\n<pre><span class=\"kn\">import<\/span> <span class=\"nn\">scipy.linalg<\/span> <span class=\"kn\">as<\/span> <span class=\"nn\">sla<\/span>\r\n\r\n<span class=\"n\">X<\/span><span class=\"p\">,<\/span><span class=\"n\">Y<\/span> <span class=\"o\">=<\/span> <span class=\"n\">np<\/span><span class=\"o\">.<\/span><span class=\"n\">copy<\/span><span class=\"p\">(<\/span><span class=\"n\">_X<\/span><span class=\"p\">),<\/span> <span class=\"n\">np<\/span><span class=\"o\">.<\/span><span class=\"n\">copy<\/span><span class=\"p\">(<\/span><span class=\"n\">_Y<\/span><span class=\"p\">)<\/span>\r\n<span class=\"c\"># NOTE: sla.lstsq uses dgelss insead of dgelsd<\/span>\r\n<span class=\"o\">%<\/span><span class=\"k\">timeit<\/span> sla.lstsq(X,Y,overwrite_a=True,overwrite_b=True)\r\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"output_wrapper\">\n<div class=\"output\">\n<div class=\"output_area\">\n<div class=\"prompt\"><\/div>\n<div class=\"output_subarea output_stream output_stdout output_text\">\n<pre>1 loops, best of 3: 28.7 s per loop\r\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<p>A few things stand out here.<\/p>\n<p>Even though <code>np.linalg.lstsq<\/code> makes a copy (and it also has additional memory overhead to return its other results &#8211; it doesn&#8217;t overwrite), it is still faster at this problem size. I&#8217;ll tinker with finding a tradeoff point, but remember the point here was (largely) about memory and less about processing time (although the two are intimately intertwined).<\/p>\n<p>My Python <code>sls<\/code> functions are not particularly optimized. The <code>sls_qr_via_dgels<\/code> function is very small. The <code>sls_svd_via_manual_dgelsd<\/code>, however, has a lot of machinery on the inside. I&#8217;ve left some of that machinery out of this post (which is already quite long and detailed). But, there is memory allocation, workspace (temporary) size calculation, and a variety of utility routines that I&#8217;ve only lightly tuned. Most of those are only executed once, so we&#8217;re not talking about inner loop performance hits.<\/p>\n<p>Next, while <code>scipy.linalg.lstsq<\/code> can operate with overwriting, it uses a different underlying algorithm <code>dgelss<\/code> instead of <code>dgelsd<\/code>. That algorithm is a bit slower (than <code>dgelsd<\/code>), but it uses less memory.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<h3 id=\"lapack-glossary\">LAPACK Glossary<\/h3>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<p>There are many LAPACK routine names in the post. Here&#8217;s a quick guide to interpreting them.<\/p>\n<p>Important abbreviations:<\/p>\n<ul>\n<li><code>d<\/code>: the matrix is made of <code>double<\/code> elements (double precision floating point)<\/li>\n<li><code>ge<\/code>: the matrix is a &quot;general&quot; matrix. It has no more specific structure like diagonal, triangular, orthogonal, etc.<\/li>\n<li><code>or<\/code>: we are dealing with an &quot;orthogonal&quot; matrix (the nicest propery of orthogonal matrices is that the inverse is equal to the, easy to compute, transpose)<\/li>\n<li><code>lqf<\/code>, <code>qrf<\/code>: <span class=\"math inline\">\\(LQ\\)<\/span> and <span class=\"math inline\">\\(QR\\)<\/span> &quot;factorizations&quot; (aka, decompositions)<\/li>\n<li><code>ls<\/code>: perform least squares<\/li>\n<\/ul>\n<p>After some practice, you will start &quot;seeing&quot; the abbreviations embedded in the six letter FORTRAN names.<\/p>\n<table>\n<thead>\n<tr class=\"header\">\n<th align=\"left\">LAPACK Routine<\/th>\n<th align=\"left\">expanded name<\/th>\n<th align=\"left\">actions<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr class=\"odd\">\n<td align=\"left\">dgels<\/td>\n<td align=\"left\">double general least squares<\/td>\n<td align=\"left\">solves least squares with <span class=\"math inline\">\\(QR\\)<\/span><\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">dgebrd<\/td>\n<td align=\"left\">double general bidiagonalization<\/td>\n<td align=\"left\">$X QBP^T $ with Q,P orthogonal and B bidiagonal<\/td>\n<\/tr>\n<tr class=\"odd\">\n<td align=\"left\">dormbr<\/td>\n<td align=\"left\">double orthogonal multiply <code>br<\/code><\/td>\n<td align=\"left\">multiply a matrix by the Q or P produced from <code>dgebrd<\/code><\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">dlalsd<\/td>\n<td align=\"left\">double <code>la<\/code> least squares svd (?)<\/td>\n<td align=\"left\">solve least squares problem on a bidiagonal matrix using <span class=\"math inline\">\\(SVD\\)<\/span><\/td>\n<\/tr>\n<tr class=\"odd\">\n<td align=\"left\">dgeqrf<\/td>\n<td align=\"left\">double general <span class=\"math inline\">\\(QR\\)<\/span> factorization<\/td>\n<td align=\"left\">&#8217;nuff said<\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">dgelqf<\/td>\n<td align=\"left\">double general <span class=\"math inline\">\\(LQ\\)<\/span> factorization<\/td>\n<td align=\"left\">&#8217;nuff said<\/td>\n<\/tr>\n<tr class=\"odd\">\n<td align=\"left\">dormqr<\/td>\n<td align=\"left\">double orthogonal multiple <code>qr<\/code><\/td>\n<td align=\"left\">multiply a matrix by the Q produced from <code>dgeqrf<\/code><\/td>\n<\/tr>\n<tr class=\"even\">\n<td align=\"left\">dormlq<\/td>\n<td align=\"left\">double orthogonal multiple <code>lq<\/code><\/td>\n<td align=\"left\">multiply a matrix by the Q produced from <code>dgelqf<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cell border-box-sizing text_cell rendered\">\n<div class=\"prompt input_prompt\">\n<\/div>\n<div class=\"inner_cell\">\n<div class=\"text_cell_render border-box-sizing rendered_html\">\n<h3>\nAdditional Resources<br \/>\n<\/h3>\n<p>\nYou can grab a <a href=\"http:\/\/drsfenner.org\/public\/notebooks\/OLS_LAPACK_and_LAPACKE_3.ipynb\">copy of this notebook<\/a>.\n<\/p>\n<p>\nEven better, you can <a href=\"http:\/\/nbviewer.ipython.org\/url\/drsfenner.org\/public\/notebooks\/OLS_LAPACK_and_LAPACKE_3.ipynb\">view it using nbviewer<\/a>.\n<\/p>\n<h3>\nLicense<br \/>\n<\/h3>\n<p>\nUnless otherwise noted, the contents of this notebook are under the following license. The code in the notebook should be considered part of the text (i.e., licensed and treated as as follows).\n<\/p>\n<p><a rel=\"license\" href=\"http:\/\/creativecommons.org\/licenses\/by-nc-sa\/4.0\/\"><img decoding=\"async\" alt=\"Creative Commons License\" style=\"border-width:0\" src=\"https:\/\/i.creativecommons.org\/l\/by-nc-sa\/4.0\/88x31.png\" \/><\/a><br \/><span xmlns:dct=\"http:\/\/purl.org\/dc\/terms\/\" property=\"dct:title\">DrsFenner.org Blog And Notebooks<\/span> by <a xmlns:cc=\"http:\/\/creativecommons.org\/ns#\" href=\"drsfenner.org\" property=\"cc:attributionName\" rel=\"cc:attributionURL\">Mark and Barbara Fenner<\/a> is licensed under a <a rel=\"license\" href=\"http:\/\/creativecommons.org\/licenses\/by-nc-sa\/4.0\/\">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License<\/a>.<br \/>Permissions beyond the scope of this license may be available at <a xmlns:cc=\"http:\/\/creativecommons.org\/ns#\" href=\"drsfenner.org\/blog\/about-and-contacts\" rel=\"cc:morePermissions\">drsfenner.org\/blog\/about-and-contacts<\/a>.<\/p>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>In the last post, we tried to use lower-level LAPACK\/LAPACKE calls to remove some of the copying overhead of our operations. Well, it turns out that it didn&#8217;t work out so well (see the table at the end of the post). The fundamental reason why is that LAPACKE took the easy way out, when they [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6,7],"tags":[],"class_list":["post-407","post","type-post","status-publish","format-standard","hentry","category-mrdr","category-sci-math-stat-python"],"_links":{"self":[{"href":"http:\/\/drsfenner.org\/blog\/wp-json\/wp\/v2\/posts\/407","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/drsfenner.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/drsfenner.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/drsfenner.org\/blog\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"http:\/\/drsfenner.org\/blog\/wp-json\/wp\/v2\/comments?post=407"}],"version-history":[{"count":2,"href":"http:\/\/drsfenner.org\/blog\/wp-json\/wp\/v2\/posts\/407\/revisions"}],"predecessor-version":[{"id":429,"href":"http:\/\/drsfenner.org\/blog\/wp-json\/wp\/v2\/posts\/407\/revisions\/429"}],"wp:attachment":[{"href":"http:\/\/drsfenner.org\/blog\/wp-json\/wp\/v2\/media?parent=407"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/drsfenner.org\/blog\/wp-json\/wp\/v2\/categories?post=407"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/drsfenner.org\/blog\/wp-json\/wp\/v2\/tags?post=407"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}