PageSourceSearch

https://www.manopt.org/firstexample.html

html manopt.org collected 2026-09-24 10:04:21 UTC 57,171 bytes, 1,266 lines download raw bytes

1<!DOCTYPE html>
2<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head>
3
4<meta charset="utf-8">
5<meta name="generator" content="quarto-1.4.557">
6
7<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
8
9
10<title>Manopt - A first example</title>
11<style>
12code{white-space: pre-wrap;}
13span.smallcaps{font-variant: small-caps;}
14div.columns{display: flex; gap: min(4vw, 1.5em);}
15div.column{flex: auto; overflow-x: auto;}
16div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
17ul.task-list{list-style: none;}
18ul.task-list li input[type="checkbox"] {
19  width: 0.8em;
20  margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */ 
21  vertical-align: middle;
22}
23/* CSS for syntax highlighting */
24pre > code.sourceCode { white-space: pre; position: relative; }
25pre > code.sourceCode > span { line-height: 1.25; }
26pre > code.sourceCode > span:empty { height: 1.2em; }
27.sourceCode { overflow: visible; }
28code.sourceCode > span { color: inherit; text-decoration: inherit; }
29div.sourceCode { margin: 1em 0; }
30pre.sourceCode { margin: 0; }
31@media screen {
32div.sourceCode { overflow: auto; }
33}
34@media print {
35pre > code.sourceCode { white-space: pre-wrap; }
36pre > code.sourceCode > span { display: inline-block; text-indent: -5em; padding-left: 5em; }
37}
38pre.numberSource code
39  { counter-reset: source-line 0; }
40pre.numberSource code > span
41  { position: relative; left: -4em; counter-increment: source-line; }
42pre.numberSource code > span > a:first-child::before
43  { content: counter(source-line);
44    position: relative; left: -1em; text-align: right; vertical-align: baseline;
45    border: none; display: inline-block;
46    -webkit-touch-callout: none; -webkit-user-select: none;
47    -khtml-user-select: none; -moz-user-select: none;
48    -ms-user-select: none; user-select: none;
49    padding: 0 4px; width: 4em;
50  }
51pre.numberSource { margin-left: 3em;  padding-left: 4px; }
52div.sourceCode
53  {   }
54@media screen {
55pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
56}
57</style>
58
59
60<script src="site_libs/quarto-nav/quarto-nav.js"></script>
vendor: 1 bytes, line 60
60
61<script src="site_libs/quarto-nav/headroom.min.js"></script>
vendor: 1 bytes, line 61
61
62<script src="site_libs/clipboard/clipboard.min.js"></script>
vendor: 1 bytes, line 62
62
63<script src="site_libs/quarto-search/autocomplete.umd.js"></script>
vendor: 1 bytes, line 63
63
64<script src="site_libs/quarto-search/fuse.min.js"></script>
vendor: 1 bytes, line 64
64
65<script src="site_libs/quarto-search/quarto-search.js"></script>
65
66<meta name="quarto:offset" content="./">
67<link href="./icon.png" rel="icon" type="image/png">
68<script src="site_libs/cookie-consent/cookie-consent.js"></script>
68
69<link href="site_libs/cookie-consent/cookie-consent.css" rel="stylesheet">
70<script src="site_libs/quarto-html/quarto.js"></script>
vendor: 1 bytes, line 70
70
71<script src="site_libs/quarto-html/popper.min.js"></script>
vendor: 1 bytes, line 71
71
72<script src="site_libs/quarto-html/tippy.umd.min.js"></script>
vendor: 1 bytes, line 72
72
73<script src="site_libs/quarto-html/anchor.min.js"></script>
73
74<link href="site_libs/quarto-html/tippy.css" rel="stylesheet">
75<link href="site_libs/quarto-html/quarto-syntax-highlighting.css" rel="stylesheet" class="quarto-color-scheme" id="quarto-text-highlighting-styles">
76<link href="site_libs/quarto-html/quarto-syntax-highlighting-dark.css" rel="prefetch" class="quarto-color-scheme quarto-color-alternate" id="quarto-text-highlighting-styles">
77<script src="site_libs/bootstrap/bootstrap.min.js"></script>
77
78<link href="site_libs/bootstrap/bootstrap-icons.css" rel="stylesheet">
79<link href="site_libs/bootstrap/bootstrap.min.css" rel="stylesheet" class="quarto-color-scheme" id="quarto-bootstrap" data-mode="light">
80<link href="site_libs/bootstrap/bootstrap-dark.min.css" rel="prefetch" class="quarto-color-scheme quarto-color-alternate" id="quarto-bootstrap" data-mode="dark">
81<script id="quarto-search-options" type="application/json">{
82  "location": "navbar",
83  "copy-button": false,
84  "collapse-after": 3,
85  "panel-placement": "end",
86  "type": "overlay",
87  "limit": 50,
88  "keyboard-shortcut": [
89    "f",
90    "/",
91    "s"
92  ],
93  "language": {
94    "search-no-results-text": "No results",
95    "search-matching-documents-text": "matching documents",
96    "search-copy-link-title": "Copy link to search",
97    "search-hide-matches-text": "Hide additional matches",
98    "search-more-match-text": "more match in this document",
99    "search-more-matches-text": "more matches in this document",
100    "search-clear-button-title": "Clear",
101    "search-text-placeholder": "",
102    "search-detached-cancel-button-title": "Cancel",
103    "search-submit-button-title": "Submit",
104    "search-label": "Search"
105  }
106}</script>
vendor: 67 bytes, lines 106-107
106
107<script async="" src="https://www.googletagmanager.com/gtag/js?id=
107G-71FCE8T97E
vendor: 13 bytes, lines 107-108
107"></script>
108
109<script type="text/plain" cookie-consent="tracking">
110
vendor: 127 bytes, lines 111-114
111window.dataLayer = window.dataLayer || [];
112function gtag(){dataLayer.push(arguments);}
113gtag('js', new Date());
114gtag('config', '
114G-71FCE8T97E
vendor: 29 bytes, line 114
114', { 'anonymize_ip': true});
115</script>
115
116
117<script type="text/javascript" charset="UTF-8">
118document.addEventListener('DOMContentLoaded', function () {
119cookieconsent.run({
120  "notice_banner_type":"simple",
121  "consent_type":"express",
122  "palette":"light",
123  "language":"en",
124  "page_load_consent_levels":["strictly-necessary"],
125  "notice_banner_reject_button_hide":false,
126  "preferences_center_close_button_hide":false,
127  "website_name":""
128  ,
129"language":"en"
130  });
131});
132</script>
132 
133  
134<style>html{ scroll-behavior: smooth; }</style>
135<script>
136
137window.MathJax = {
138
139    tex: {
140
141      inlineMath: [['$', '$'], ['\\(', '\\)']],
142
143      macros: {
144
145        reals: "\\mathbb{R}",
146
147        inner: ["\\left\\langle #1, #2 \\right\\rangle", 2],
148
149        smallinner: ["\\langle #1, #2 \\rangle", 2],
150
151        innersmall: ["\\langle #1, #2 \\rangle", 2],
152
153        innerbig: ["\\big\\langle #1, #2 \\big\\rangle", 2],
154
155        sqfrobnorm: ["\\left\\|#1\\right\\|_{\\mathrm{F}}^2", 1],
156
157        sqfrobnormsmall: ["\\|#1\\|_{\\mathrm{F}}^2", 1],
158
159        sqfrobnormbig: ["\\big\\|#1\\big\\|_{\\mathrm{F}}^2", 1],
160
161        calM: "\\mathcal{M}",
162
163        calN: "\\mathcal{N}",
164
165        calA: "\\mathcal{A}",
166
167        calX: "\\mathcal{X}",
168
169        T: "\\mathrm{T}",
170
171        skeww: "\\mathrm{skew}",
172
173        symm: "\\mathrm{sym}",
174
175        Sym: "\\mathrm{Sym}",
176
177        Proj: "\\mathrm{Proj}",
178
179        grad: "\\mathrm{grad}",
180
181        Hess: "\\mathrm{Hess}",
182
183        Retr: "\\mathrm{R}",
184
185        D: "\\mathrm{D}",
186
187        calE: "\\mathcal{E}",
188
189        Stnp: "\\mathrm{St}(n,p)",
190
191        Rnp: "\\mathbb{R}^{n \\times p}",
192
193        Rmp: "\\mathbb{R}^{m \\times p}",
194
195        Rnr: "\\mathbb{R}^{n \\times r}",
196
197        Rrn: "\\mathbb{R}^{r \\times n}",
198
199        Rmr: "\\mathbb{R}^{m \\times r}",
200
201        Rnn: "\\mathbb{R}^{n \\times n}",
202
203        Rmn: "\\mathbb{R}^{m \\times n}",
204
205        Rnm: "\\mathbb{R}^{n \\times m}",
206
207        Rpp: "\\mathbb{R}^{p \\times p}",
208
209        Rmnr: "\\mathbb{R}^{m \\times n}_r",
210
211        Rmnlr: "\\mathbb{R}^{m \\times n}_{\\leq r}",
212
213        Rn: "\\mathbb{R}^{n}",
214
215        Rm: "\\mathbb{R}^{m}",
216
217        Rp: "\\mathbb{R}^{p}",
218
219        Rd: "\\mathbb{R}^{d}",
220
221        Rk: "\\mathbb{R}^{k}",
222
223        Sn: "\\mathbb{S}^{n-1}",
224
225        Sm: "\\mathbb{S}^{m-1}",
226
227        Sd: "\\mathbb{S}^{d-1}",
228
229        trace: "\\mathrm{Tr}",
230
231        Trace: "\\mathrm{Tr}",
232
233        diag: "\\mathrm{diag}",
234
235        sign: "\\mathrm{sign}",
236
237        Od: "\\mathrm{O}(d)",
238
239        On: "\\mathrm{O}(n)",
240
241        Om: "\\mathrm{O}(m)",
242
243        SOd: "\\mathrm{SO}(d)",
244
245        SOn: "\\mathrm{SO}(n)",
246
247        rank: "\\mathrm{rank}",
248
249        frakX: "\\mathfrak{X}",
250
251        frakF: "\\mathfrak{F}",
252
253        One: "\\mathbf{1}",
254
255        dist: "\\mathrm{dist}",
256
257        calP: "\\mathcal{P}",
258
259        calK: "\\mathcal{K}",
260
261        Rdd: "\\mathbb{R}^{d \\times d}",
262
263        calU: "\\mathcal{U}",
264
265        calV: "\\mathcal{V}",
266
267        calW: "\\mathcal{W}",
268
269        Exp: "\\mathrm{Exp}",
270
271        Log: "\\mathrm{Log}",
272
273        inj: "\\mathrm{inj}",
274
275        dom: "\\operatorname{dom}",
276
277        bigger: "<",
278
279        dt: "\\mathrm{d}t",
280
281        transpose: "^\\top\\! ",
282
283        sigmamax: "\\sigma_{\\mathrm{max}}",
284
285        sigmamin: "\\sigma_{\\mathrm{min}}",
286
287        lambdamax: "\\lambda_{\\mathrm{max}}",
288
289        lambdamin: "\\lambda_{\\mathrm{min}}",
290
291        TODO: ["\\textcolor{red}{[\\textrm{#1}]}", 1]
292
293      }
294
295    },
296
297    svg: {
298
299      fontCache: 'global'
300
301    }
302
303  };
304
305</script>
305
306
307
308
309<script>
310
311  window.addEventListener('load', function() {
312
313    document.getElementById("cc-nb-title").innerHTML = "May we use cookies for anonymized analytics?";
314
315    // to improve your browsing experience on our website, 
316
317    document.getElementById("cc-nb-text").innerHTML = "We would like to use cookies to analyze our website traffic, and to understand where our visitors are coming from.";
318
319  });
320
321</script>
321
322
323
324  
324<script src="https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=es6"></script>
324
325  
325<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js" type="text/javascript"></script>
325
326
327<script type="text/javascript">
328const typesetMath = (el) => {
329  if (window.MathJax) {
330    // MathJax Typeset
331    window.MathJax.typeset([el]);
332  } else if (window.katex) {
333    // KaTeX Render
334    var mathElements = el.getElementsByClassName("math");
335    var macros = [];
336    for (var i = 0; i < mathElements.length; i++) {
337      var texText = mathElements[i].firstChild;
338      if (mathElements[i].tagName == "SPAN") {
339        window.katex.render(texText.data, mathElements[i], {
340          displayMode: mathElements[i].classList.contains('display'),
341          throwOnError: false,
342          macros: macros,
343          fleqn: false
344        });
345      }
346    }
347  }
348}
349window.Quarto = {
350  typesetMath
351};
352</script>
352
353
354<link rel="stylesheet" href="styles.css">
355<meta property="og:title" content="Manopt - A first example">
356<meta property="og:description" content="">
357<meta property="og:site_name" content="Manopt">
358</head>
359
360<body class="nav-sidebar docked nav-fixed fullcontent">
361
362<div id="quarto-search-results"></div>
363  <header id="quarto-header" class="headroom fixed-top">
364    <nav class="navbar navbar-expand-lg " data-bs-theme="dark">
365      <div class="navbar-container container-fluid">
366      <div class="navbar-brand-container mx-auto">
367    <a class="navbar-brand" href="./index.html">
368    <span class="navbar-title">Manopt</span>
369    </a>
370  </div>
371            <div id="quarto-search" class="" title="Search"></div>
372          <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">
373  <span class="navbar-toggler-icon"></span>
374</button>
375          <div class="collapse navbar-collapse" id="navbarCollapse">
376            <ul class="navbar-nav navbar-nav-scroll me-auto">
377  <li class="nav-item">
378    <a class="nav-link" href="./index.html"> 
379<span class="menu-text">Home</span></a>
380  </li>  
381  <li class="nav-item dropdown ">
382    <a class="nav-link dropdown-toggle" href="#" id="nav-menu-tutorial" role="button" data-bs-toggle="dropdown" aria-expanded="false">
383 <span class="menu-text">Tutorial</span>
384    </a>
385    <ul class="dropdown-menu" aria-labelledby="nav-menu-tutorial">    
386        <li>
387    <a class="dropdown-item" href="./gettingstarted.html">
388 <span class="dropdown-text">Getting started</span></a>
389  </li>  
390        <li>
391    <a class="dropdown-item" href="./firstexample.html">
392 <span class="dropdown-text">A first example</span></a>
393  </li>  
394        <li>
395    <a class="dropdown-item" href="./manifolds.html">
396 <span class="dropdown-text">Manifolds</span></a>
397  </li>  
398        <li>
399    <a class="dropdown-item" href="./costdescription.html">
400 <span class="dropdown-text">Cost functions</span></a>
401  </li>  
402        <li>
403    <a class="dropdown-item" href="./solvers.html">
404 <span class="dropdown-text">Solvers</span></a>
405  </li>  
406        <li>
407    <a class="dropdown-item" href="./tools.html">
408 <span class="dropdown-text">Helpful tools</span></a>
409  </li>  
410        <li>
411    <a class="dropdown-item" href="./lifts.html">
412 <span class="dropdown-text">Lifts</span></a>
413  </li>  
414        <li>
415    <a class="dropdown-item" href="./core.html">
416 <span class="dropdown-text">Core tools</span></a>
417  </li>  
418    </ul>
419  </li>
420  <li class="nav-item">
421    <a class="nav-link" href="./learning.html"> 
422<span class="menu-text">Learning</span></a>
423  </li>  
424  <li class="nav-item">
425    <a class="nav-link" href="./downloads.html"> 
426<span class="menu-text">Downloads</span></a>
427  </li>  
428  <li class="nav-item">
429    <a class="nav-link" href="./forum.html"> 
430<span class="menu-text">Forum</span></a>
431  </li>  
432  <li class="nav-item">
433    <a class="nav-link" href="./about.html"> 
434<span class="menu-text">About</span></a>
435  </li>  
436</ul>
437            <ul class="navbar-nav navbar-nav-scroll ms-auto">
438  <li class="nav-item compact">
439    <a class="nav-link" href="https://github.com/NicolasBoumal/manopt" target="_blank"> <i class="bi bi-github" role="img">
440</i> 
441<span class="menu-text"></span></a>
442  </li>  
443  <li class="nav-item compact">
444    <a class="nav-link" href="mailto:[email protected]"> <i class="bi bi-envelope-at" role="img" aria-label="E-mail">
445</i> 
446<span class="menu-text"></span></a>
447  </li>  
448</ul>
449          </div> <!-- /navcollapse -->
450          <div class="quarto-navbar-tools">
451  <a href="" class="quarto-color-scheme-toggle quarto-navigation-tool  px-1" onclick="window.quartoToggleColorScheme(); return false;" title="Toggle dark mode"><i class="bi"></i></a>
452</div>
453      </div> <!-- /container-fluid -->
454    </nav>
455  <nav class="quarto-secondary-nav">
456    <div class="container-fluid d-flex">
457      <button type="button" class="quarto-btn-toggle btn" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item" aria-controls="quarto-sidebar" aria-expanded="false" aria-label="Toggle sidebar navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">
458        <i class="bi bi-layout-text-sidebar-reverse"></i>
459      </button>
460        <nav class="quarto-page-breadcrumbs" aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="./firstexample.html">A first example</a></li></ol></nav>
461        <a class="flex-grow-1" role="button" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item" aria-controls="quarto-sidebar" aria-expanded="false" aria-label="Toggle sidebar navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">      
462        </a>
463    </div>
464  </nav>
465</header>
466<!-- content -->
467<div id="quarto-content" class="quarto-container page-columns page-rows-contents page-layout-article page-navbar">
468<!-- sidebar -->
469  <nav id="quarto-sidebar" class="sidebar collapse collapse-horizontal quarto-sidebar-collapse-item sidebar-navigation docked overflow-auto">
470    <div class="sidebar-menu-container"> 
471    <ul class="list-unstyled mt-1">
472        <li class="sidebar-item">
473  <div class="sidebar-item-container"> 
474  <a href="./gettingstarted.html" class="sidebar-item-text sidebar-link">
475 <span class="menu-text">Getting started</span></a>
476  </div>
477</li>
478        <li class="sidebar-item">
479  <div class="sidebar-item-container"> 
480  <a href="./firstexample.html" class="sidebar-item-text sidebar-link active">
481 <span class="menu-text">A first example</span></a>
482  </div>
483</li>
484        <li class="sidebar-item">
485  <div class="sidebar-item-container"> 
486  <a href="./manifolds.html" class="sidebar-item-text sidebar-link">
487 <span class="menu-text">Manifolds</span></a>
488  </div>
489</li>
490        <li class="sidebar-item">
491  <div class="sidebar-item-container"> 
492  <a href="./costdescription.html" class="sidebar-item-text sidebar-link">
493 <span class="menu-text">Cost functions</span></a>
494  </div>
495</li>
496        <li class="sidebar-item">
497  <div class="sidebar-item-container"> 
498  <a href="./solvers.html" class="sidebar-item-text sidebar-link">
499 <span class="menu-text">Solvers</span></a>
500  </div>
501</li>
502        <li class="sidebar-item">
503  <div class="sidebar-item-container"> 
504  <a href="./tools.html" class="sidebar-item-text sidebar-link">
505 <span class="menu-text">Helpful tools</span></a>
506  </div>
507</li>
508        <li class="sidebar-item">
509  <div class="sidebar-item-container"> 
510  <a href="./lifts.html" class="sidebar-item-text sidebar-link">
511 <span class="menu-text">Lifts</span></a>
512  </div>
513</li>
514        <li class="sidebar-item">
515  <div class="sidebar-item-container"> 
516  <a href="./core.html" class="sidebar-item-text sidebar-link">
517 <span class="menu-text">Core tools</span></a>
518  </div>
519</li>
520    </ul>
521    </div>
522</nav>
523<div id="quarto-sidebar-glass" class="quarto-sidebar-collapse-item" data-bs-toggle="collapse" data-bs-target=".quarto-sidebar-collapse-item"></div>
524<!-- margin-sidebar -->
525    
526<!-- main -->
527<main class="content" id="quarto-document-content">
528
529<header id="title-block-header" class="quarto-title-block default">
530<div class="quarto-title">
531<h1 class="title">A first example</h1>
532</div>
533
534
535
536<div class="quarto-title-meta">
537
538    
539  
540    
541  </div>
542  
543
544
545</header>
546
547
548<p>Let us first illustrate the most commonly useful features of Manopt using a standard optimization problem on a sphere.</p>
549<section id="the-math" class="level2">
550<h2 class="anchored" data-anchor-id="the-math">The math</h2>
551<p>Let <span class="math inline">\(A \in \mathbb{R}^{n\times n}\)</span> be a symmetric matrix. The eigenvectors of <span class="math inline">\(A\)</span> associated to its largest eigenvalue <a href="https://en.wikipedia.org/wiki/Rayleigh_quotient">are known to be</a> the optimizers for the following optimization problem:</p>
552<p><span class="math display">\[\max\limits_{x\in\mathbb{R}^n, x \neq 0} \frac{x^\top A x}{x^\top x}.\]</span></p>
553<p>The cost function is insensitive to the norm of <span class="math inline">\(x\)</span>, hence we might as well fix it to <span class="math inline">\(x^\top x = \|x\|^2 = 1\)</span>. Also, we would like to have a minimization problem rather than a maximization problem (we’ll see why in a moment). Accordingly, we flip the sign of the cost function. In the end, we want to solve:</p>
554<p><span class="math display">\[\min\limits_{x\in\mathbb{R}^n, \|x\| = 1} -x^\top A x.\]</span></p>
555<p>The cost function and its gradient in <span class="math inline">\(\mathbb{R}^n\)</span> are:</p>
556<p><span class="math display">\[  
557\begin{align*}
558&nbsp;&nbsp;&nbsp; f(x) = -x^\top A x, &amp;&amp;
559&nbsp;&nbsp;&nbsp; \nabla f(x) = -2Ax.
560\end{align*}
561\]</span></p>
562<p>(See the <a href="https://www.matrixcalculus.org/">matrix calculus</a> website for help with figuring out gradients. You can also learn the math to do these computations in Section 4.7 of <a href="https://www.nicolasboumal.net/book">this book</a>.)</p>
563<p>The constraint on the vector <span class="math inline">\(x\)</span> tells us that <span class="math inline">\(x\)</span> is a point on the sphere (one of the nicest manifolds):</p>
564<p><span class="math display">\[\mathbb{S}^{n-1} = \{x \in \mathbb{R}^n : x^\top x = 1\}.\]</span></p>
565<p>This is all the information we need to apply Manopt to our problem. For additional theory, see the <a href="./costdescription.html">cost function</a> page and the <a href="./learning.html">learning</a> page.</p>
566</section>
567<section id="the-code" class="level2">
568<h2 class="anchored" data-anchor-id="the-code">The code</h2>
569<p>Solving this optimization problem using Manopt requires a few lines of Matlab code. Here they are, and explanations follow.</p>
570<div class="sourceCode" id="cb1"><pre class="sourceCode matlab code-with-copy"><code class="sourceCode matlab"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co">% Generate random problem data.</span></span>
571<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="va">n</span> <span class="op">=</span> <span class="fl">1000</span><span class="op">;</span></span>
572<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="va">A</span> <span class="op">=</span> <span class="va">randn</span>(<span class="va">n</span>)<span class="op">;</span></span>
573<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="va">A</span> <span class="op">=</span> <span class="fl">.5</span><span class="op">*</span>(<span class="va">A</span><span class="op">+</span><span class="va">A</span><span class="op">'</span>)<span class="op">;</span></span>
574<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a></span>
575<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="co">% Create the problem structure.</span></span>
576<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="va">manifold</span> <span class="op">=</span> <span class="va">spherefactory</span>(<span class="va">n</span>)<span class="op">;</span></span>
577<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="va">problem</span>.<span class="va">M</span> <span class="op">=</span> <span class="va">manifold</span><span class="op">;</span></span>
578<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a></span>
579<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="co">% Define the problem cost function and its Euclidean gradient.</span></span>
580<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="va">problem</span>.<span class="va">cost</span>  <span class="op">=</span> <span class="op">@</span>(<span class="va">x</span>) <span class="op">-</span><span class="va">x</span><span class="op">'*</span>(<span class="va">A</span><span class="op">*</span><span class="va">x</span>)<span class="op">;</span></span>
581<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="va">problem</span>.<span class="va">egrad</span> <span class="op">=</span> <span class="op">@</span>(<span class="va">x</span>) <span class="op">-</span><span class="fl">2</span><span class="op">*</span><span class="va">A</span><span class="op">*</span><span class="va">x</span><span class="op">;</span>      <span class="co">% notice the 'e' in 'egrad' for Euclidean</span></span>
582<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a></span>
583<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a><span class="co">% Numerically check gradient consistency (just once, optional).</span></span>
584<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a><span class="va">checkgradient</span>(<span class="va">problem</span>)<span class="op">;</span> <span class="va">pause</span><span class="op">;</span></span>
585<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a></span>
586<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a><span class="co">% Solve.</span></span>
587<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a>[<span class="va">x</span><span class="op">,</span> <span class="va">xcost</span><span class="op">,</span> <span class="va">info</span><span class="op">,</span> <span class="va">options</span>] <span class="op">=</span> <span class="va">trustregions</span>(<span class="va">problem</span>)<span class="op">;</span></span>
588<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a></span>
589<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a><span class="co">% Display some statistics.</span></span>
590<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a><span class="va">figure</span><span class="op">;</span></span>
591<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a><span class="va">semilogy</span>([<span class="va">info</span>.<span class="va">iter</span>]<span class="op">,</span> [<span class="va">info</span>.<span class="va">gradnorm</span>]<span class="op">,</span> <span class="ss">'.-'</span>)<span class="op">;</span></span>
592<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a><span class="va">xlabel</span>(<span class="ss">'Iteration number'</span>)<span class="op">;</span></span>
593<span id="cb1-24"><a href="#cb1-24" aria-hidden="true" tabindex="-1"></a><span class="va">ylabel</span>(<span class="ss">'Norm of the Riemannian gradient of f'</span>)<span class="op">;</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
594<p>Let us look at the code bit by bit. First, we generate some data for our problem. Then, we execute these two lines:</p>
595<div class="sourceCode" id="cb2"><pre class="sourceCode matlab code-with-copy"><code class="sourceCode matlab"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="va">manifold</span> <span class="op">=</span> <span class="va">spherefactory</span>(<span class="va">n</span>)<span class="op">;</span></span>
596<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="va">problem</span>.<span class="va">M</span> <span class="op">=</span> <span class="va">manifold</span><span class="op">;</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
597<p>The call to <a href="https://github.com/NicolasBoumal/manopt/tree/master/manopt/manifolds/sphere">spherefactory</a> returns a structure describing the manifold <span class="math inline">\(\mathbb{S}^{n-1}\)</span>, i.e., the sphere. This manifold corresponds to the constraint appearing in our optimization problem. For other constraints, take a look at the <a href="./manifolds.html">various supported manifolds</a>.</p>
598<p>The second instruction creates a structure named <code>problem</code> and sets the field <code>problem.M</code> to contain the manifold structure.</p>
599<p>The <code>problem</code> structure is further populated with everything a solver might need to know about the problem in order to solve it, such as the cost function and its gradient:</p>
600<div class="sourceCode" id="cb3"><pre class="sourceCode matlab code-with-copy"><code class="sourceCode matlab"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="va">problem</span>.<span class="va">cost</span>  <span class="op">=</span> <span class="op">@</span>(<span class="va">x</span>) <span class="op">-</span><span class="va">x</span><span class="op">'*</span>(<span class="va">A</span><span class="op">*</span><span class="va">x</span>)<span class="op">;</span></span>
601<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="va">problem</span>.<span class="va">egrad</span> <span class="op">=</span> <span class="op">@</span>(<span class="va">x</span>) <span class="op">-</span><span class="fl">2</span><span class="op">*</span><span class="va">A</span><span class="op">*</span><span class="va">x</span><span class="op">;</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
602<p>The cost function and its derivatives are specified as <a href="https://ch.mathworks.com/help/matlab/ref/function_handle.html">function handles</a>. Notice how the gradient was specified as the <em>Euclidean</em> gradient of <span class="math inline">\(f\)</span>, i.e., <span class="math inline">\(\nabla f(x) = -2Ax\)</span> in the function <code>egrad</code> (mind the <code>e</code>). The conversion to the Riemannian gradient happens automatically behind the scene. This is especially useful with more complicated manifolds.</p>
603<p>We could also define the cost and its derivatives in several other ways, e.g., to avoid the redundant computation of the product <code>A*x</code>: see the <a href="./costdescription.html">cost description</a> page. In particular, with Manopt 7.0 and Matlab R2021a or later, if you have the Deep Learning Toolbox, then you can also use <strong>automatic differentiation</strong> (AD) instead of defining the gradient (and even the Hessian) yourself:</p>
604<div class="sourceCode" id="cb4"><pre class="sourceCode matlab code-with-copy"><code class="sourceCode matlab"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co">% problem.egrad = @(x) -2*A*x; -- we can replace that line with:</span></span>
605<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="va">problem</span> <span class="op">=</span> <span class="va">manoptAD</span>(<span class="va">problem</span>)<span class="op">;</span> <span class="co">% automatic differentiation</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
606<p>See the <a href="./costdescription.html">cost description</a> page for more information. Keep in mind that, while AD is convenient and reasonably efficie
606nt, it is usually slower than (good) hand-written code. </p>
607<p>The next instruction is not needed to solve the problem but often helps at the prototyping stage:</p>
608<div class="sourceCode" id="cb5"><pre class="sourceCode matlab code-with-copy"><code class="sourceCode matlab"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="va">checkgradient</span>(<span class="va">problem</span>)<span class="op">;</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
609<p>The <a href="https://github.com/NicolasBoumal/manopt/blob/master/manopt/tools/checkgradient.m">checkgradient</a> tool verifies numerically that the cost function and its gradient agree up to the appropriate order. See the <a href="./tools.html">tools page</a> for further details and more helpful tools offered by Manopt. This tool generates the following figure:</p>
610<div class="quarto-figure quarto-figure-center">
611<figure class="figure">
612<p><img src="images/tutorial-gradientcheck-dark.png" class="img-fluid figure-img" style="width:35em"></p>
613<figcaption>The <code>checkgradient</code> tool outputs a figure and text in the command line. In the figure here, we see that part of the continuous curve has a slope that matches that of the dashed line: that’s what we like to see. If not, then it is likely that the gradient is incorrectly implemented. Try it: change <code>problem.egrad</code> to <code>@(x) A*x</code> for example and see what happens.</figcaption>
614</figure>
615</div>
616<p>The blue curve seems to have the same slope as the dashed line over a decent segment (highlighted in orange): that’s what we want to see. Also check the text output in the command prompt.</p>
617<p>We now run one of <a href="./solvers.html">the solvers</a> on our problem:</p>
618<div class="sourceCode" id="cb6"><pre class="sourceCode matlab code-with-copy"><code class="sourceCode matlab"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a>[<span class="va">x</span><span class="op">,</span> <span class="va">xcost</span><span class="op">,</span> <span class="va">info</span><span class="op">,</span> <span class="va">options</span>] <span class="op">=</span> <span class="va">trustregions</span>(<span class="va">problem</span>)<span class="op">;</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
619<p>This instruction calls <a href="https://github.com/NicolasBoumal/manopt/blob/master/manopt/solvers/trustregions/trustregions.m"><code>trustregions</code></a>, without initial guess and without options structure. As a result, the solver generates a random initial guess automatically and resorts to the default values for all options. As a general feature in Manopt, all options are, well, optional.</p>
620<div class="callout callout-style-simple callout-note callout-titled">
621<div class="callout-header d-flex align-content-center">
622<div class="callout-icon-container">
623<i class="callout-icon"></i>
624</div>
625<div class="callout-title-container flex-fill">
626Manopt always minimizes
627</div>
628</div>
629<div class="callout-body-container callout-body">
630<p>All solvers in Manopt aim to <em>minimize</em> the cost function in the problem structure. If you want to <em>maximize</em> a function, do as we did earlier on this page: flip the sign of <span class="math inline">\(f\)</span> (and likewise for its derivatives).</p>
631</div>
632</div>
633<p>The returned values are:</p>
634<ul>
635<li><code>x</code>: usually an approximate local minimizer of the cost function,</li>
636<li><code>xcost</code>: the value of <span class="math inline">\(f\)</span> at <code>x</code>,</li>
637<li><code>info</code>: a struct-array containing information about the successive iterations performed by the solver, and</li>
638<li><code>options</code>: a structure containing the options used and their values: peek inside to find out what you can parameterize.</li>
639</ul>
640<p>For more details and more solvers, see the <a href="./solvers.html">solvers</a> page.</p>
641<p>This call issues a warning:</p>
642<blockquote class="blockquote">
643<p><code>Warning: No Hessian provided. Using FD approximation.</code></p>
644</blockquote>
645<p>That is because the trust-regions algorithm normally requires the Hessian of the cost function to be provided in the problem structure. When the Hessian is not provided, Manopt approximates it using finite differences on the gradient and it warns y
645ou about it. This is mostly fine. You may disable this warning by calling <code>warning('off', 'manopt:getHessian:approx');</code>.</p>
646<p>Finally, we access the contents of the struct-array <code>info</code> to display a convergence plot:</p>
647<div class="sourceCode" id="cb7"><pre class="sourceCode matlab code-with-copy"><code class="sourceCode matlab"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="va">semilogy</span>([<span class="va">info</span>.<span class="va">iter</span>]<span class="op">,</span> [<span class="va">info</span>.<span class="va">gradnorm</span>]<span class="op">,</span> <span class="ss">'.-'</span>)<span class="op">;</span>  </span>
648<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="va">xlabel</span>(<span class="ss">'Iteration number'</span>)<span class="op">;</span>  </span>
649<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="va">ylabel</span>(<span class="ss">'Norm of the gradient of f'</span>)<span class="op">;</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
650<p>This generates the following figure. For more information on what data is stored in <code>info</code>, look inside and see the <a href="./solvers.html">solvers</a> page.</p>
651<div class="quarto-figure quarto-figure-center">
652<figure class="figure">
653<p><img src="images/tutorial-gradientnorm-dark.png" class="img-fluid figure-img" style="width:35em"></p>
654<figcaption>The Riemannian gradient norm is converging to zero fast: that’s what we like to see.</figcaption>
655</figure>
656</div>
657<div class="callout callout-style-simple callout-note callout-titled">
658<div class="callout-header d-flex align-content-center">
659<div class="callout-icon-container">
660<i class="callout-icon"></i>
661</div>
662<div class="callout-title-container flex-fill">
663<code>info</code> is a struct-array
664</div>
665</div>
666<div class="callout-body-container callout-body">
667<p>Notice that we write <code>[info.xyz]</code> with brackets, and not simply <code>info.xyz</code>. This is because <code>info</code> is a struct-array. Read this <a href="https://blogs.mathworks.com/loren/2007/04/19/vectorizing-access-to-an-array-of-structures/">MathWorks blog post</a> for further information.</p>
668</div>
669</div>
670<div class="callout callout-style-simple callout-note callout-titled">
671<div class="callout-header d-flex align-content-center">
672<div class="callout-icon-container">
673<i class="callout-icon"></i>
674</div>
675<div class="callout-title-container flex-fill">
676You may omit the gradient, but…
677</div>
678</div>
679<div class="callout-body-container callout-body">
680<p>In the example above, we specified the gradient but not the Hessian. As a result, Manopt automatically uses finite differences (FD) to approximate the Hessian if needed. This is fine.</p>
681<p>If we also do not specify the gradient, then Manopt approximates that with FD as well. The solver will run, but this is slow. The approximation is good enough that it is convenient to have as a feature for prototyping on low-dimensional manifolds, but it should not be used for anything serious. If you do, you may want to set <code>options.tolgradnorm</code> to a larger value (say, <code>1e-4</code>) and pass the <code>options</code> structure to the solver.</p>
682</div>
683</div>
684
685
686</section>
687
688</main> <!-- /main -->
689<script id="quarto-html-after-body" type="application/javascript">
690window.document.addEventListener("DOMContentLoaded", function (event) {
691  const toggleBodyColorMode = (bsSheetEl) => {
692    const mode = bsSheetEl.getAttribute("data-mode");
693    const bodyEl = window.document.querySelector("body");
694    if (mode === "dark") {
695      bodyEl.classList.add("quarto-dark");
696      bodyEl.classList.remove("quarto-light");
697    } else {
698      bodyEl.classList.add("quarto-light");
699      bodyEl.classList.remove("quarto-dark");
700    }
701  }
702  const toggleBodyColorPrimary = () => {
703    const bsSheetEl = window.document.querySelector("link#quarto-bootstrap");
704    if (bsSheetEl) {
705      toggleBodyColorMode(bsSheetEl);
706    }
707  }
708  toggleBodyColorPrimary();  
709  const disableStylesheet = (stylesheets) => {
710    for (let i=0; i < stylesheets.length; i++) {
711      const stylesheet = stylesheets[i];
712      stylesheet.rel = 'prefetch';
713    }
714  }
715  const enableStylesheet = (stylesheets) => {
716    for (let i=0; i < stylesheets.length; i++) {
717      const stylesheet = stylesheets[i];
718      stylesheet.rel = 'stylesheet';
719    }
720  }
721  const manageTransitions = (selector, allowTransitions) => {
722    const els = window.document.querySelectorAll(selector);
723    for (let i=0; i < els.length; i++) {
724      const el = els[i];
725      if (allowTransitions) {
726        el.classList.remove('notransition');
727      } else {
728        el.classList.add('notransition');
729      }
730    }
731  }
732  const toggleGiscusIfUsed = (isAlternate, darkModeDefault) => {
733    const baseTheme = document.querySelector('#giscus-base-theme')?.value ?? 'light';
734    const alternateTheme = document.querySelector('#giscus-alt-theme')?.value ?? 'dark';
735    let newTheme = '';
736    if(darkModeDefault) {
737      newTheme = isAlternate ? baseTheme : alternateTheme;
738    } else {
739      newTheme = isAlternate ? alternateTheme : baseTheme;
740    }
741    const changeGiscusTheme = () => {
742      // From: https://github.com/giscus/giscus/issues/336
743      const sendMessage = (message) => {
744        const iframe = document.querySelector('iframe.giscus-frame');
745        if (!iframe) return;
746        iframe.contentWindow.postMessage({ giscus: message }, 'https://giscus.app');
747      }
748      sendMessage({
749        setConfig: {
750          theme: newTheme
751        }
752      });
753    }
754    const isGiscussLoaded = window.document.querySelector('iframe.giscus-frame') !== null;
755    if (isGiscussLoaded) {
756      changeGiscusTheme();
757    }
758  }
759  const toggleColorMode = (alternate) => {
760    // Switch the stylesheets
761    const alternateStylesheets = window.document.querySelectorAll('link.quarto-color-scheme.quarto-color-alternate');
762    manageTransitions('#quarto-margin-sidebar .nav-link', false);
763    if (alternate) {
764      enableStylesheet(alternateStylesheets);
765      for (const sheetNode of alternateStylesheets) {
766        if (sheetNode.id === "quarto-bootstrap") {
767          toggleBodyColorMode(sheetNode);
768        }
769      }
770    } else {
771      disableStylesheet(alternateStylesheets);
772      toggleBodyColorPrimary();
773    }
774    manageTransitions('#quarto-margin-sidebar .nav-link', true);
775    // Switch the toggles
776    const toggles = window.document.querySelectorAll('.quarto-color-scheme-toggle');
777    for (let i=0; i < toggles.length; i++) {
778      const toggle = toggles[i];
779      if (toggle) {
780        if (alternate) {
781          toggle.classList.add("alternate");     
782        } else {
783          toggle.classList.remove("alternate");
784        }
785      }
786    }
787    // Hack to workaround the fact that safari doesn't
788    // properly recolor the scrollbar when toggling (#1455)
789    if (navigator.userAgent.indexOf('Safari') > 0 && navigator.userAgent.indexOf('Chrome') == -1) {
790      manageTransitions("body", false);
791      window.scrollTo(0, 1);
792      setTimeout(() => {
793        window.scrollTo(0, 0);
794        manageTransitions("body", true);
795      }, 40);  
796    }
797  }
798  const isFileUrl = () => { 
799    return window.location.protocol === 'file:';
800  }
801  const hasAlternateSentinel = () => {  
802    let styleSentinel = getColorSchemeSentinel();
803    if (styleSentinel !== null) {
804      return styleSentinel === "alternate";
805    } else {
806      return false;
807    }
808  }
809  const setStyleSentinel = (alternate) => {
810    const value = alternate ? "alternate" : "default";
811    if (!isFileUrl()) {
812      window.localStorage.setItem("quarto-color-scheme", value);
813    } else {
814      localAlternateSentinel = value;
815    }
816  }
817  const getColorSchemeSentinel = () => {
818    if (!isFileUrl()) {
819      const storageValue = window.localStorage.getItem("quarto-color-scheme");
820      return storageValue != null ? storageValue : localAlternateSentinel;
821    } else {
822      return localAlternateSentinel;
823    }
824  }
825  const darkModeDefault = false;
826  let localAlternateSentinel = darkModeDefault ? 'alternate' : 'default';
827  // Dark / light mode switch
828  window.quartoToggleColorScheme = () => {
829    // Read the current dark / light value 
830    let toAlternate = !hasAlternateSentinel();
831    toggleColorMode(toAlternate);
832    setStyleSentinel(toAlternate);
833    toggleGiscusIfUsed(toAlternate, darkModeDefault);
834  };
835  // Ensure there is a toggle, if there isn't float one in the top right
836  if (window.document.querySelector('.quarto-color-scheme-toggle') === null) {
837    const a = window.document.createElement('a');
838    a.classList.add('top-right');
839    a.classList.add('quarto-color-scheme-toggle');
840    a.href = "";
841    a.onclick = function() { try { window.quartoToggleColorScheme(); } catch {} return false; };
842    const i = window.document.createElement("i");
843    i.classList.add('bi');
844    a.appendChild(i);
845    window.document.body.appendChild(a);
846  }
847  // Switch to dark mode if need be
848  if (hasAlternateSentinel()) {
849    toggleColorMode(true);
850  } else {
851    toggleColorMode(false);
852  }
853  const icon = "";
854  const anchorJS = new window.AnchorJS();
855  anchorJS.options = {
856    placement: 'right',
857    icon: icon
858  };
859  anchorJS.add('.anchored');
860  const isCodeAnnotation = (el) => {
861    for (const clz of el.classList) {
862      if (clz.startsWith('code-annotation-')) {                     
863        return true;
864      }
865    }
866    return false;
867  }
868  const clipboard = new window.ClipboardJS('.code-copy-button', {
869    text: function(trigger) {
870      const codeEl = trigger.previousElementSibling.cloneNode(true);
871      for (const childEl of codeEl.children) {
872        if (isCodeAnnotation(childEl)) {
873          childEl.remove();
874        }
875      }
876      return codeEl.innerText;
877    }
878  });
879  clipboard.on('success', function(e) {
880    // button target
881    const button = e.trigger;
882    // don't keep focus
883    button.blur();
884    // flash "checked"
885    button.classList.add('code-copy-button-checked');
886    var currentTitle = button.getAttribute("title");
887    button.setAttribute("title", "Copied!");
888    let tooltip;
889    if (window.bootstrap) {
890      button.setAttribute("data-bs-toggle", "tooltip");
891      button.setAttribute("data-bs-placement", "left");
892      button.setAttribute("data-bs-title", "Copied!");
893      tooltip = new bootstrap.Tooltip(button, 
894        { trigger: "manual", 
895          customClass: "code-copy-button-tooltip",
896          offset: [0, -8]});
897      tooltip.show();    
898    }
899    setTimeout(function() {
900      if (tooltip) {
901        tooltip.hide();
902        button.removeAttribute("data-bs-title");
903        button.removeAttribute("data-bs-toggle");
904        button.removeAttribute("data-bs-placement");
905      }
906      button.setAttribute("title", currentTitle);
907      button.classList.remove('code-copy-button-checked');
908    }, 1000);
909    // clear code selection
910    e.clearSelection();
911  });
912    var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//);
913    var mailtoRegex = new RegExp(/^mailto:/);
914      var filterRegex = new RegExp("https:\/\/www\.manopt\.org");
915    var isInternal = (href) => {
916        return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href);
917    }
918    // Inspect non-navigation links and adorn them if external
919 	var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool)');
920    for (var i=0; i<links.length; i++) {
921      const link = links[i];
922      if (!isInternal(link.href)) {
923        // undo the damage that might have been done by quarto-nav.js in the case of
924        // links that we want to consider external
925        if (link.dataset.originalHref !== undefined) {
926          link.href = link.dataset.originalHref;
927        }
928          // target, if specified
929          link.setAttribute("target", "_blank");
930          if (link.getAttribute("rel") === null) {
931            link.setAttribute("rel", "noopener");
932          }
933      }
934    }
935  function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) {
936    const config = {
937      allowHTML: true,
938      maxWidth: 500,
939      delay: 100,
940      arrow: false,
941      appendTo: function(el) {
942          return el.parentElement;
943      },
944      interactive: true,
945      interactiveBorder: 10,
946      theme: 'quarto',
947      placement: 'bottom-start',
948    };
949    if (contentFn) {
950      config.content = contentFn;
951    }
952    if (onTriggerFn) {
953      config.onTrigger = onTriggerFn;
954    }
955    if (onUntriggerFn) {
956      config.onUntrigger = onUntriggerFn;
957    }
958    window.tippy(el, config); 
959  }
960  const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]');
961  for (var i=0; i<noterefs.length; i++) {
962    const ref = noterefs[i];
963    tippyHover(ref, function() {
964      // use id or data attribute instead here
965      let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href');
966      try { href = new URL(href).hash; } catch {}
967      const id = href.replace(/^#\/?/, "");
968      const note = window.document.getElementById(id);
969      if (note) {
970        return note.innerHTML;
971      } else {
972        return "";
973      }
974    });
975  }
976  const xrefs = window.document.querySelectorAll('a.quarto-xref');
977  const processXRef = (id, note) => {
978    // Strip column container classes
979    const stripColumnClz = (el) => {
980      el.classList.remove("page-full", "page-columns");
981      if (el.children) {
982        for (const child of el.children) {
983          stripColumnClz(child);
984        }
985      }
986    }
987    stripColumnClz(note)
988    if (id === null || id.startsWith('sec-')) {
989      // Special case sections, only their first couple elements
990      const container = document.createElement("div");
991      if (note.children && note.children.length > 2) {
992        container.appendChild(note.children[0].cloneNode(true));
993        for (let i = 1; i < note.children.length; i++) {
994          const child = note.children[i];
995          if (child.tagName === "P" && child.innerText === "") {
996            continue;
997          } else {
998            container.appendChild(child.cloneNode(true));
999            break;
1000          }
1001        }
1002        if (window.Quarto?.typesetMath) {
1003          window.Quarto.typesetMath(container);
1004        }
1005        return container.innerHTML
1006      } else {
1007        if (window.Quarto?.typesetMath) {
1008          window.Quarto.typesetMath(note);
1009        }
1010        return note.innerHTML;
1011      }
1012    } else {
1013      // Remove any anchor links if they are present
1014      const anchorLink = note.querySelector('a.anchorjs-link');
1015      if (anchorLink) {
1016        anchorLink.remove();
1017      }
1018      if (window.Quarto?.typesetMath) {
1019        window.Quarto.typesetMath(note);
1020      }
1021      // TODO in 1.5, we should make sure this works without a callout special case
1022      if (note.classList.contains("callout")) {
1023        return note.outerHTML;
1024      } else {
1025        return note.innerHTML;
1026      }
1027    }
1028  }
1029  for (var i=0; i<xrefs.length; i++) {
1030    const xref = xrefs[i];
1031    tippyHover(xref, undefined, function(instance) {
1032      instance.disable();
1033      let url = xref.getAttribute('href');
1034      let hash = undefined; 
1035      if (url.startsWith('#')) {
1036        hash = url;
1037      } else {
1038        try { hash = new URL(url).hash; } catch {}
1039      }
1040      if (hash) {
1041        const id = hash.replace(/^#\/?/, "");
1042        const note = window.document.getElementById(id);
1043        if (note !== null) {
1044          try {
1045            const html = processXRef(id, note.cloneNode(true));
1046            instance.setContent(html);
1047          } finally {
1048            instance.enable();
1049            instance.show();
1050          }
1051        } else {
1052          // See if we can fetch this
1053          fetch(url.split('#')[0])
1054          .then(res => res.text())
1055          .then(html => {
1056            const parser = new DOMParser();
1057            const htmlDoc = parser.parseFromString(html, "text/html");
1058            const note = htmlDoc.getElementById(id);
1059            if (note !== null) {
1060              const html = processXRef(id, note);
1061              instance.setContent(html);
1062            } 
1063          }).finally(() => {
1064            instance.enable();
1065            instance.show();
1066          });
1067        }
1068      } else {
1069        // See if we can fetch a full url (with no hash to target)
1070        // This is a special case and we should probably do some content thinning / targeting
1071        fetch(url)
1072        .then(res => res.text())
1073        .then(html => {
1074          const parser = new DOMParser();
1075          const htmlDoc = parser.parseFromString(html, "text/html");
1076          const note = htmlDoc.querySelector('main.content');
1077          if (note !== null) {
1078            // This should only happen for chapter cross references
1079            // (since there is no id in the URL)
1080            // remove the first header
1081            if (note.children.length > 0 && note.children[0].tagName === "HEADER") {
1082              note.children[0].remove();
1083            }
1084            const html = processXRef(null, note);
1085            instance.setContent(html);
1086          } 
1087        }).finally(() => {
1088          instance.enable();
1089          instance.show();
1090        });
1091      }
1092    }, function(instance) {
1093    });
1094  }
1095      let selectedAnnoteEl;
1096      const selectorForAnnotation = ( cell, annotation) => {
1097        let cellAttr = 'data-code-cell="' + cell + '"';
1098        let lineAttr = 'data-code-annotation="' +  annotation + '"';
1099        const selector = 'span[' + cellAttr + '][' + lineAttr + ']';
1100        return selector;
1101      }
1102      const selectCodeLines = (annoteEl) => {
1103        const doc = window.document;
1104        const targetCell = annoteEl.getAttribute("data-target-cell");
1105        const targetAnnotation = annoteEl.getAttribute("data-target-annotation");
1106        const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation));
1107        const lines = annoteSpan.getAttribute("data-code-lines").split(",");
1108        const lineIds = lines.map((line) => {
1109          return targetCell + "-" + line;
1110        })
1111        let top = null;
1112        let height = null;
1113        let parent = null;
1114        if (lineIds.length > 0) {
1115            //compute the position of the single el (top and bottom and make a div)
1116            const el = window.document.getElementById(lineIds[0]);
1117            top = el.offsetTop;
1118            height = el.offsetHeight;
1119            parent = el.parentElement.parentElement;
1120          if (lineIds.length > 1) {
1121            const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]);
1122            const bottom = lastEl.offsetTop + lastEl.offsetHeight;
1123            height = bottom - top;
1124          }
1125          if (top !== null && height !== null && parent !== null) {
1126            // cook up a div (if necessary) and position it 
1127            let div = window.document.getElementById("code-annotation-line-highlight");
1128            if (div === null) {
1129              div = window.document.createElement("div");
1130              div.setAttribute("id", "code-annotation-line-highlight");
1131              div.style.position = 'absolute';
1132              parent.appendChild(div);
1133            }
1134            div.style.top = top - 2 + "px";
1135            div.style.height = height + 4 + "px";
1136            div.style.left = 0;
1137            let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter");
1138            if (gutterDiv === null) {
1139              gutterDiv = window.document.createElement("div");
1140              gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter");
1141              gutterDiv.style.position = 'absolute';
1142              const codeCell = window.document.getElementById(targetCell);
1143              const gutter = codeCell.querySelector('.code-annotation-gutter');
1144              gutter.appendChild(gutterDiv);
1145            }
1146            gutterDiv.style.top = top - 2 + "px";
1147            gutterDiv.style.height = height + 4 + "px";
1148          }
1149          selectedAnnoteEl = annoteEl;
1150        }
1151      };
1152      const unselectCodeLines = () => {
1153        const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"];
1154        elementsIds.forEach((elId) => {
1155          const div = window.document.getElementById(elId);
1156          if (div) {
1157            div.remove();
1158          }
1159        });
1160        selectedAnnoteEl = undefined;
1161      };
1162        // Handle positioning of the toggle
1163    window.addEventListener(
1164      "resize",
1165      throttle(() => {
1166        elRect = undefined;
1167        if (selectedAnnoteEl) {
1168          selectCodeLines(selectedAnnoteEl);
1169        }
1170      }, 10)
1171    );
1172    function throttle(fn, ms) {
1173    let throttle = false;
1174    let timer;
1175      return (...args) => {
1176        if(!throttle) { // first call gets through
1177            fn.apply(this, args);
1178            throttle = true;
1179        } else { // all the others get throttled
1180            if(timer) clearTimeout(timer); // cancel #2
1181            timer = setTimeout(() => {
1182              fn.apply(this, args);
1183              timer = throttle = false;
1184            }, ms);
1185        }
1186      };
1187    }
1188      // Attach click handler to the DT
1189      const annoteDls = window.document.querySelectorAll('dt[data-target-cell]');
1190      for (const annoteDlNode of annoteDls) {
1191        annoteDlNode.addEventListener('click', (event) => {
1192          const clickedEl = event.target;
1193          if (clickedEl !== selectedAnnoteEl) {
1194            unselectCodeLines();
1195            const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active');
1196            if (activeEl) {
1197              activeEl.classList.remove('code-annotation-active');
1198            }
1199            selectCodeLines(clickedEl);
1200            clickedEl.classList.add('code-annotation-active');
1201          } else {
1202            // Unselect the line
1203            unselectCodeLines();
1204            clickedEl.classList.remove('code-annotation-active');
1205          }
1206        });
1207      }
1208  const findCites = (el) => {
1209    const parentEl = el.parentElement;
1210    if (parentEl) {
1211      const cites = parentEl.dataset.cites;
1212      if (cites) {
1213        return {
1214          el,
1215          cites: cites.split(' ')
1216        };
1217      } else {
1218        return findCites(el.parentElement)
1219      }
1220    } else {
1221      return undefined;
1222    }
1223  };
1224  var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]');
1225  for (var i=0; i<bibliorefs.length; i++) {
1226    const ref = bibliorefs[i];
1227    const citeInfo = findCites(ref);
1228    if (citeInfo) {
1229      tippyHover(citeInfo.el, function() {
1230        var popup = window.document.createElement('div');
1231        citeInfo.cites.forEach(function(cite) {
1232          var citeDiv = window.document.createElement('div');
1233          citeDiv.classList.add('hanging-indent');
1234          citeDiv.classList.add('csl-entry');
1235          var biblioDiv = window.document.getElementById('ref-' + cite);
1236          if (biblioDiv) {
1237            citeDiv.innerHTML = biblioDiv.innerHTML;
1238          }
1239          popup.appendChild(citeDiv);
1240        });
1241        return popup.innerHTML;
1242      });
1243    }
1244  }
1245});
1246</script>
1246
1247</div> <!-- /content -->
1248<footer class="footer">
1249  <div class="nav-footer">
1250    <div class="nav-footer-left">
1251      &nbsp;
1252    </div>   
1253    <div class="nav-footer-center">
1254
1255<div class="cookie-consent-footer"><a href="#" id="open_preferences_center">Cookie preferences -- it helps us to know which pages are read the most</a></div></div>
1256    <div class="nav-footer-right">
1257      &nbsp;
1258    </div>
1259  </div>
1260</footer>
1261
1262
1263
1264
1265<script src="site_libs/quarto-html/zenscroll-min.js"></script>
1265
1266</body></html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.