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 f(x) = -x^\top A x, && 559 \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 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 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.