+
+ Read the Docs
+ v: ${config.versions.current.slug}
+
+
+
+
+ ${renderLanguages(config)}
+ ${renderVersions(config)}
+ ${renderDownloads(config)}
+
+ On Read the Docs
+
+ Project Home
+
+
+ Builds
+
+
+ Downloads
+
+
+
+ Search
+
+
+
+
+
+
+ Hosted by Read the Docs
+
+
+
+ `;
+
+ // Inject the generated flyout into the body HTML element.
+ document.body.insertAdjacentHTML("beforeend", flyout);
+
+ // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout.
+ document
+ .querySelector("#flyout-search-form")
+ .addEventListener("focusin", () => {
+ const event = new CustomEvent("readthedocs-search-show");
+ document.dispatchEvent(event);
+ });
+ })
+}
+
+if (themeLanguageSelector || themeVersionSelector) {
+ function onSelectorSwitch(event) {
+ const option = event.target.selectedIndex;
+ const item = event.target.options[option];
+ window.location.href = item.dataset.url;
+ }
+
+ document.addEventListener("readthedocs-addons-data-ready", function (event) {
+ const config = event.detail.data();
+
+ const versionSwitch = document.querySelector(
+ "div.switch-menus > div.version-switch",
+ );
+ if (themeVersionSelector) {
+ let versions = config.versions.active;
+ if (config.versions.current.hidden || config.versions.current.type === "external") {
+ versions.unshift(config.versions.current);
+ }
+ const versionSelect = `
+
+ ${versions
+ .map(
+ (version) => `
+
+ ${version.slug}
+ `,
+ )
+ .join("\n")}
+
+ `;
+
+ versionSwitch.innerHTML = versionSelect;
+ versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch);
+ }
+
+ const languageSwitch = document.querySelector(
+ "div.switch-menus > div.language-switch",
+ );
+
+ if (themeLanguageSelector) {
+ if (config.projects.translations.length) {
+ // Add the current language to the options on the selector
+ let languages = config.projects.translations.concat(
+ config.projects.current,
+ );
+ languages = languages.sort((a, b) =>
+ a.language.name.localeCompare(b.language.name),
+ );
+
+ const languageSelect = `
+
+ ${languages
+ .map(
+ (language) => `
+
+ ${language.language.name}
+ `,
+ )
+ .join("\n")}
+
+ `;
+
+ languageSwitch.innerHTML = languageSelect;
+ languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch);
+ }
+ else {
+ languageSwitch.remove();
+ }
+ }
+ });
+}
+
+document.addEventListener("readthedocs-addons-data-ready", function (event) {
+ // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav.
+ document
+ .querySelector("[role='search'] input")
+ .addEventListener("focusin", () => {
+ const event = new CustomEvent("readthedocs-search-show");
+ document.dispatchEvent(event);
+ });
+});
\ No newline at end of file
diff --git a/_static/language_data.js b/_static/language_data.js
new file mode 100644
index 0000000..367b8ed
--- /dev/null
+++ b/_static/language_data.js
@@ -0,0 +1,199 @@
+/*
+ * language_data.js
+ * ~~~~~~~~~~~~~~~~
+ *
+ * This script contains the language-specific data used by searchtools.js,
+ * namely the list of stopwords, stemmer, scorer and splitter.
+ *
+ * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"];
+
+
+/* Non-minified version is copied as a separate JS file, if available */
+
+/**
+ * Porter Stemmer
+ */
+var Stemmer = function() {
+
+ var step2list = {
+ ational: 'ate',
+ tional: 'tion',
+ enci: 'ence',
+ anci: 'ance',
+ izer: 'ize',
+ bli: 'ble',
+ alli: 'al',
+ entli: 'ent',
+ eli: 'e',
+ ousli: 'ous',
+ ization: 'ize',
+ ation: 'ate',
+ ator: 'ate',
+ alism: 'al',
+ iveness: 'ive',
+ fulness: 'ful',
+ ousness: 'ous',
+ aliti: 'al',
+ iviti: 'ive',
+ biliti: 'ble',
+ logi: 'log'
+ };
+
+ var step3list = {
+ icate: 'ic',
+ ative: '',
+ alize: 'al',
+ iciti: 'ic',
+ ical: 'ic',
+ ful: '',
+ ness: ''
+ };
+
+ var c = "[^aeiou]"; // consonant
+ var v = "[aeiouy]"; // vowel
+ var C = c + "[^aeiouy]*"; // consonant sequence
+ var V = v + "[aeiou]*"; // vowel sequence
+
+ var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
+ var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
+ var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
+ var s_v = "^(" + C + ")?" + v; // vowel in stem
+
+ this.stemWord = function (w) {
+ var stem;
+ var suffix;
+ var firstch;
+ var origword = w;
+
+ if (w.length < 3)
+ return w;
+
+ var re;
+ var re2;
+ var re3;
+ var re4;
+
+ firstch = w.substr(0,1);
+ if (firstch == "y")
+ w = firstch.toUpperCase() + w.substr(1);
+
+ // Step 1a
+ re = /^(.+?)(ss|i)es$/;
+ re2 = /^(.+?)([^s])s$/;
+
+ if (re.test(w))
+ w = w.replace(re,"$1$2");
+ else if (re2.test(w))
+ w = w.replace(re2,"$1$2");
+
+ // Step 1b
+ re = /^(.+?)eed$/;
+ re2 = /^(.+?)(ed|ing)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ re = new RegExp(mgr0);
+ if (re.test(fp[1])) {
+ re = /.$/;
+ w = w.replace(re,"");
+ }
+ }
+ else if (re2.test(w)) {
+ var fp = re2.exec(w);
+ stem = fp[1];
+ re2 = new RegExp(s_v);
+ if (re2.test(stem)) {
+ w = stem;
+ re2 = /(at|bl|iz)$/;
+ re3 = new RegExp("([^aeiouylsz])\\1$");
+ re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
+ if (re2.test(w))
+ w = w + "e";
+ else if (re3.test(w)) {
+ re = /.$/;
+ w = w.replace(re,"");
+ }
+ else if (re4.test(w))
+ w = w + "e";
+ }
+ }
+
+ // Step 1c
+ re = /^(.+?)y$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = new RegExp(s_v);
+ if (re.test(stem))
+ w = stem + "i";
+ }
+
+ // Step 2
+ re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ suffix = fp[2];
+ re = new RegExp(mgr0);
+ if (re.test(stem))
+ w = stem + step2list[suffix];
+ }
+
+ // Step 3
+ re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ suffix = fp[2];
+ re = new RegExp(mgr0);
+ if (re.test(stem))
+ w = stem + step3list[suffix];
+ }
+
+ // Step 4
+ re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
+ re2 = /^(.+?)(s|t)(ion)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = new RegExp(mgr1);
+ if (re.test(stem))
+ w = stem;
+ }
+ else if (re2.test(w)) {
+ var fp = re2.exec(w);
+ stem = fp[1] + fp[2];
+ re2 = new RegExp(mgr1);
+ if (re2.test(stem))
+ w = stem;
+ }
+
+ // Step 5
+ re = /^(.+?)e$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = new RegExp(mgr1);
+ re2 = new RegExp(meq1);
+ re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
+ if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
+ w = stem;
+ }
+ re = /ll$/;
+ re2 = new RegExp(mgr1);
+ if (re.test(w) && re2.test(w)) {
+ re = /.$/;
+ w = w.replace(re,"");
+ }
+
+ // and turn initial Y back to y
+ if (firstch == "y")
+ w = firstch.toLowerCase() + w.substr(1);
+ return w;
+ }
+}
+
diff --git a/_static/minipres.js b/_static/minipres.js
new file mode 100644
index 0000000..ad11c87
--- /dev/null
+++ b/_static/minipres.js
@@ -0,0 +1,223 @@
+// Add goTo method to elements
+// http://stackoverflow.com/questions/4801655/how-to-go-to-a-specific-element-on-page
+(function($) {
+ $.fn.goTo = function() {
+ $('html, body').animate({
+ scrollTop: $(this).offset().top //+ 'px'
+ }, 'fast');
+ return this; // for chaining...
+ }
+})(jQuery);
+
+// NO good way to do this!. Copy a hack from here
+// https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
+// https://stackoverflow.com/a/2880929
+var urlParams;
+(window.onpopstate = function () {
+ var match,
+ pl = /\+/g, // Regex for replacing addition symbol with a space
+ search = /([^&=]+)=?([^&]*)/g,
+ decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
+ query = window.location.search.substring(1);
+ urlParams = {};
+ while (match = search.exec(query))
+ urlParams[decode(match[1])] = decode(match[2]);
+})();
+
+// Select heading levels
+var maxHeading = urlParams['h']
+if (maxHeading === undefined) maxHeading = 2
+var headingLevels = [];
+for (h=2 ; h
(sections.length-1) ) {
+ // if we would scroll past bottom, or above top, do nothing
+ return;
+ }
+
+ console.log('xxxxxx');
+ var targetSection = sections[targetPos];
+ console.log(targetSection, typeof(targetSection));
+
+ // Return targetSection top and height
+ var secProperties = section_top_and_height(targetSection);
+ var top = secProperties['top'];
+ var height = secProperties['height']
+ var win_height = window.innerHeight;
+ //console.info(top, height, win_height)
+
+ var scroll_to = 0;
+ if (height >= win_height || height == 0) {
+ scroll_to = top;
+ } else {
+ scroll_to = top - (win_height-height)/3.;
+ }
+ //console.info(top, height, win_height, scroll_to)
+
+ $('html, body').animate({
+ scrollTop: scroll_to //+ 'px'
+ }, 'fast');
+
+}
+
+
+function minipres() {
+ /* Enable the minipres mode:
+ - call the hide() function
+ - set up the scrolling listener
+ */
+ document.addEventListener('keydown', function (event) {
+ switch(event.which) {
+ case 37: // left
+ switch_slide(-1);
+ event.preventDefault();
+ return false;
+ break;
+ //case 38: // up
+ case 39: // right
+ switch_slide(+1);
+ event.preventDefault();
+ return false;
+ break;
+ //case 40: // down
+ default:
+ return; // exit this handler for other keys
+ }
+ }, true)
+
+ hide()
+
+ // Increase space between sections
+ //$("div .section").css('margin-bottom', '50%');
+ $(sectionSelector).css('margin-top', '50%');
+
+ // Reduce size/color of other sections
+ if (hiddenSectionSelector.length > 0) {
+ var hideNodes = $(hiddenSectionSelector);
+ console.log(typeof hideNodes, hideNodes);
+ for (node in hideNodes) {
+ console.log("a", typeof node, node);
+ node = hideNodes[node]; // what's right way to iterate values?
+ console.log("b", typeof node, node);
+ if (node.parentNode && node.parentNode.className == "section") {
+ node = node.parentNode;
+ console.log("c", typeof node, node);
+ //node.css['transform'] = 'scale(.5)';
+ //node.css['transform-origin'] = 'top center';
+ $(node).css('color', 'lightgrey');
+ //$(node).css('font-size', '20%');
+ //$(node).css('visibility', 'collapse');
+ //ntahousnatouhasno;
+ }
+ }
+ }
+}
+
+function hide() {
+ /* Hide all non-essential elements on the page
+ */
+
+ // This is for sphinx_rst_theme and readthedocs
+ $(".wy-nav-side").remove();
+ $(".wy-nav-content-wrap").css('margin-left', 0);
+ $('.rst-versions').remove(); // readthedocs version selector
+
+ // Add other formats here.
+}
+
+
+var slideshow = minipres;
+
+if (window.location.search.match(/[?&](minipres|slideshow|pres)([=&]|$)/) ) {
+ //minipres()
+ window.addEventListener("load", minipres);
+} else if (window.location.search.match(/[?&](plain)([=&]|$)/) ) {
+ window.addEventListener("load", hide);
+}
diff --git a/_static/minus.png b/_static/minus.png
new file mode 100644
index 0000000..d96755f
Binary files /dev/null and b/_static/minus.png differ
diff --git a/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css b/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css
new file mode 100644
index 0000000..3356631
--- /dev/null
+++ b/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css
@@ -0,0 +1,2342 @@
+/* Variables */
+:root {
+ --mystnb-source-bg-color: #f7f7f7;
+ --mystnb-stdout-bg-color: #fcfcfc;
+ --mystnb-stderr-bg-color: #fdd;
+ --mystnb-traceback-bg-color: #fcfcfc;
+ --mystnb-source-border-color: #ccc;
+ --mystnb-source-margin-color: green;
+ --mystnb-stdout-border-color: #f7f7f7;
+ --mystnb-stderr-border-color: #f7f7f7;
+ --mystnb-traceback-border-color: #ffd6d6;
+ --mystnb-hide-prompt-opacity: 70%;
+ --mystnb-source-border-radius: .4em;
+ --mystnb-source-border-width: 1px;
+}
+
+/* Whole cell */
+div.container.cell {
+ padding-left: 0;
+ margin-bottom: 1em;
+}
+
+/* Removing all background formatting so we can control at the div level */
+.cell_input div.highlight,
+.cell_output pre,
+.cell_input pre,
+.cell_output .output {
+ border: none;
+ box-shadow: none;
+}
+
+.cell_output .output pre,
+.cell_input pre {
+ margin: 0px;
+}
+
+/* Input cells */
+div.cell div.cell_input,
+div.cell details.above-input>summary {
+ padding-left: 0em;
+ padding-right: 0em;
+ border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid;
+ background-color: var(--mystnb-source-bg-color);
+ border-left-color: var(--mystnb-source-margin-color);
+ border-left-width: medium;
+ border-radius: var(--mystnb-source-border-radius);
+}
+
+div.cell_input>div,
+div.cell_output div.output>div.highlight {
+ margin: 0em !important;
+ border: none !important;
+}
+
+/* All cell outputs */
+.cell_output {
+ padding-left: 1em;
+ padding-right: 0em;
+ margin-top: 1em;
+}
+
+/* Text outputs from cells */
+.cell_output .output.text_plain,
+.cell_output .output.traceback,
+.cell_output .output.stream,
+.cell_output .output.stderr {
+ margin-top: 1em;
+ margin-bottom: 0em;
+ box-shadow: none;
+}
+
+.cell_output .output.text_plain,
+.cell_output .output.stream {
+ background: var(--mystnb-stdout-bg-color);
+ border: 1px solid var(--mystnb-stdout-border-color);
+}
+
+.cell_output .output.stderr {
+ background: var(--mystnb-stderr-bg-color);
+ border: 1px solid var(--mystnb-stderr-border-color);
+}
+
+.cell_output .output.traceback {
+ background: var(--mystnb-traceback-bg-color);
+ border: 1px solid var(--mystnb-traceback-border-color);
+}
+
+/* Collapsible cell content */
+div.cell details.above-input div.cell_input {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-top: var(--mystnb-source-border-width) var(--mystnb-source-border-color) dashed;
+}
+
+div.cell div.cell_input.above-output-prompt {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+div.cell details.above-input>summary {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ border-bottom: var(--mystnb-source-border-width) var(--mystnb-source-border-color) dashed;
+ padding-left: 1em;
+ margin-bottom: 0;
+}
+
+div.cell details.above-output>summary {
+ background-color: var(--mystnb-source-bg-color);
+ padding-left: 1em;
+ padding-right: 0em;
+ border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid;
+ border-radius: var(--mystnb-source-border-radius);
+ border-left-color: var(--mystnb-source-margin-color);
+ border-left-width: medium;
+}
+
+div.cell details.below-input>summary {
+ background-color: var(--mystnb-source-bg-color);
+ padding-left: 1em;
+ padding-right: 0em;
+ border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid;
+ border-top: none;
+ border-bottom-left-radius: var(--mystnb-source-border-radius);
+ border-bottom-right-radius: var(--mystnb-source-border-radius);
+ border-left-color: var(--mystnb-source-margin-color);
+ border-left-width: medium;
+}
+
+div.cell details.hide>summary>span {
+ opacity: var(--mystnb-hide-prompt-opacity);
+}
+
+div.cell details.hide[open]>summary>span.collapsed {
+ display: none;
+}
+
+div.cell details.hide:not([open])>summary>span.expanded {
+ display: none;
+}
+
+@keyframes collapsed-fade-in {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
+div.cell details.hide[open]>summary~* {
+ -moz-animation: collapsed-fade-in 0.3s ease-in-out;
+ -webkit-animation: collapsed-fade-in 0.3s ease-in-out;
+ animation: collapsed-fade-in 0.3s ease-in-out;
+}
+
+/* Math align to the left */
+.cell_output .MathJax_Display {
+ text-align: left !important;
+}
+
+/* Pandas tables. Pulled from the Jupyter / nbsphinx CSS */
+div.cell_output table {
+ border: none;
+ border-collapse: collapse;
+ border-spacing: 0;
+ color: black;
+ font-size: 1em;
+ table-layout: fixed;
+}
+
+div.cell_output thead {
+ border-bottom: 1px solid black;
+ vertical-align: bottom;
+}
+
+div.cell_output tr,
+div.cell_output th,
+div.cell_output td {
+ text-align: right;
+ vertical-align: middle;
+ padding: 0.5em 0.5em;
+ line-height: normal;
+ white-space: normal;
+ max-width: none;
+ border: none;
+}
+
+div.cell_output th {
+ font-weight: bold;
+}
+
+div.cell_output tbody tr:nth-child(odd) {
+ background: #f5f5f5;
+}
+
+div.cell_output tbody tr:hover {
+ background: rgba(66, 165, 245, 0.2);
+}
+
+/** source code line numbers **/
+span.linenos {
+ opacity: 0.5;
+}
+
+/* Inline text from `paste` operation */
+
+span.pasted-text {
+ font-weight: bold;
+}
+
+span.pasted-inline img {
+ max-height: 2em;
+}
+
+tbody span.pasted-inline img {
+ max-height: none;
+}
+
+/* Font colors for translated ANSI escape sequences
+Color values are copied from Jupyter Notebook
+https://github.com/jupyter/notebook/blob/52581f8eda9b319eb0390ac77fe5903c38f81e3e/notebook/static/notebook/less/ansicolors.less#L14-L21
+Background colors from
+https://nbsphinx.readthedocs.io/en/latest/code-cells.html#ANSI-Colors
+*/
+div.highlight .-Color-Bold {
+ font-weight: bold;
+}
+
+div.highlight .-Color[class*=-Black] {
+ color: #3E424D
+}
+
+div.highlight .-Color[class*=-Red] {
+ color: #E75C58
+}
+
+div.highlight .-Color[class*=-Green] {
+ color: #00A250
+}
+
+div.highlight .-Color[class*=-Yellow] {
+ color: #DDB62B
+}
+
+div.highlight .-Color[class*=-Blue] {
+ color: #208FFB
+}
+
+div.highlight .-Color[class*=-Magenta] {
+ color: #D160C4
+}
+
+div.highlight .-Color[class*=-Cyan] {
+ color: #60C6C8
+}
+
+div.highlight .-Color[class*=-White] {
+ color: #C5C1B4
+}
+
+div.highlight .-Color[class*=-BGBlack] {
+ background-color: #3E424D
+}
+
+div.highlight .-Color[class*=-BGRed] {
+ background-color: #E75C58
+}
+
+div.highlight .-Color[class*=-BGGreen] {
+ background-color: #00A250
+}
+
+div.highlight .-Color[class*=-BGYellow] {
+ background-color: #DDB62B
+}
+
+div.highlight .-Color[class*=-BGBlue] {
+ background-color: #208FFB
+}
+
+div.highlight .-Color[class*=-BGMagenta] {
+ background-color: #D160C4
+}
+
+div.highlight .-Color[class*=-BGCyan] {
+ background-color: #60C6C8
+}
+
+div.highlight .-Color[class*=-BGWhite] {
+ background-color: #C5C1B4
+}
+
+/* Font colors for 8-bit ANSI */
+
+div.highlight .-Color[class*=-C0] {
+ color: #000000
+}
+
+div.highlight .-Color[class*=-BGC0] {
+ background-color: #000000
+}
+
+div.highlight .-Color[class*=-C1] {
+ color: #800000
+}
+
+div.highlight .-Color[class*=-BGC1] {
+ background-color: #800000
+}
+
+div.highlight .-Color[class*=-C2] {
+ color: #008000
+}
+
+div.highlight .-Color[class*=-BGC2] {
+ background-color: #008000
+}
+
+div.highlight .-Color[class*=-C3] {
+ color: #808000
+}
+
+div.highlight .-Color[class*=-BGC3] {
+ background-color: #808000
+}
+
+div.highlight .-Color[class*=-C4] {
+ color: #000080
+}
+
+div.highlight .-Color[class*=-BGC4] {
+ background-color: #000080
+}
+
+div.highlight .-Color[class*=-C5] {
+ color: #800080
+}
+
+div.highlight .-Color[class*=-BGC5] {
+ background-color: #800080
+}
+
+div.highlight .-Color[class*=-C6] {
+ color: #008080
+}
+
+div.highlight .-Color[class*=-BGC6] {
+ background-color: #008080
+}
+
+div.highlight .-Color[class*=-C7] {
+ color: #C0C0C0
+}
+
+div.highlight .-Color[class*=-BGC7] {
+ background-color: #C0C0C0
+}
+
+div.highlight .-Color[class*=-C8] {
+ color: #808080
+}
+
+div.highlight .-Color[class*=-BGC8] {
+ background-color: #808080
+}
+
+div.highlight .-Color[class*=-C9] {
+ color: #FF0000
+}
+
+div.highlight .-Color[class*=-BGC9] {
+ background-color: #FF0000
+}
+
+div.highlight .-Color[class*=-C10] {
+ color: #00FF00
+}
+
+div.highlight .-Color[class*=-BGC10] {
+ background-color: #00FF00
+}
+
+div.highlight .-Color[class*=-C11] {
+ color: #FFFF00
+}
+
+div.highlight .-Color[class*=-BGC11] {
+ background-color: #FFFF00
+}
+
+div.highlight .-Color[class*=-C12] {
+ color: #0000FF
+}
+
+div.highlight .-Color[class*=-BGC12] {
+ background-color: #0000FF
+}
+
+div.highlight .-Color[class*=-C13] {
+ color: #FF00FF
+}
+
+div.highlight .-Color[class*=-BGC13] {
+ background-color: #FF00FF
+}
+
+div.highlight .-Color[class*=-C14] {
+ color: #00FFFF
+}
+
+div.highlight .-Color[class*=-BGC14] {
+ background-color: #00FFFF
+}
+
+div.highlight .-Color[class*=-C15] {
+ color: #FFFFFF
+}
+
+div.highlight .-Color[class*=-BGC15] {
+ background-color: #FFFFFF
+}
+
+div.highlight .-Color[class*=-C16] {
+ color: #000000
+}
+
+div.highlight .-Color[class*=-BGC16] {
+ background-color: #000000
+}
+
+div.highlight .-Color[class*=-C17] {
+ color: #00005F
+}
+
+div.highlight .-Color[class*=-BGC17] {
+ background-color: #00005F
+}
+
+div.highlight .-Color[class*=-C18] {
+ color: #000087
+}
+
+div.highlight .-Color[class*=-BGC18] {
+ background-color: #000087
+}
+
+div.highlight .-Color[class*=-C19] {
+ color: #0000AF
+}
+
+div.highlight .-Color[class*=-BGC19] {
+ background-color: #0000AF
+}
+
+div.highlight .-Color[class*=-C20] {
+ color: #0000D7
+}
+
+div.highlight .-Color[class*=-BGC20] {
+ background-color: #0000D7
+}
+
+div.highlight .-Color[class*=-C21] {
+ color: #0000FF
+}
+
+div.highlight .-Color[class*=-BGC21] {
+ background-color: #0000FF
+}
+
+div.highlight .-Color[class*=-C22] {
+ color: #005F00
+}
+
+div.highlight .-Color[class*=-BGC22] {
+ background-color: #005F00
+}
+
+div.highlight .-Color[class*=-C23] {
+ color: #005F5F
+}
+
+div.highlight .-Color[class*=-BGC23] {
+ background-color: #005F5F
+}
+
+div.highlight .-Color[class*=-C24] {
+ color: #005F87
+}
+
+div.highlight .-Color[class*=-BGC24] {
+ background-color: #005F87
+}
+
+div.highlight .-Color[class*=-C25] {
+ color: #005FAF
+}
+
+div.highlight .-Color[class*=-BGC25] {
+ background-color: #005FAF
+}
+
+div.highlight .-Color[class*=-C26] {
+ color: #005FD7
+}
+
+div.highlight .-Color[class*=-BGC26] {
+ background-color: #005FD7
+}
+
+div.highlight .-Color[class*=-C27] {
+ color: #005FFF
+}
+
+div.highlight .-Color[class*=-BGC27] {
+ background-color: #005FFF
+}
+
+div.highlight .-Color[class*=-C28] {
+ color: #008700
+}
+
+div.highlight .-Color[class*=-BGC28] {
+ background-color: #008700
+}
+
+div.highlight .-Color[class*=-C29] {
+ color: #00875F
+}
+
+div.highlight .-Color[class*=-BGC29] {
+ background-color: #00875F
+}
+
+div.highlight .-Color[class*=-C30] {
+ color: #008787
+}
+
+div.highlight .-Color[class*=-BGC30] {
+ background-color: #008787
+}
+
+div.highlight .-Color[class*=-C31] {
+ color: #0087AF
+}
+
+div.highlight .-Color[class*=-BGC31] {
+ background-color: #0087AF
+}
+
+div.highlight .-Color[class*=-C32] {
+ color: #0087D7
+}
+
+div.highlight .-Color[class*=-BGC32] {
+ background-color: #0087D7
+}
+
+div.highlight .-Color[class*=-C33] {
+ color: #0087FF
+}
+
+div.highlight .-Color[class*=-BGC33] {
+ background-color: #0087FF
+}
+
+div.highlight .-Color[class*=-C34] {
+ color: #00AF00
+}
+
+div.highlight .-Color[class*=-BGC34] {
+ background-color: #00AF00
+}
+
+div.highlight .-Color[class*=-C35] {
+ color: #00AF5F
+}
+
+div.highlight .-Color[class*=-BGC35] {
+ background-color: #00AF5F
+}
+
+div.highlight .-Color[class*=-C36] {
+ color: #00AF87
+}
+
+div.highlight .-Color[class*=-BGC36] {
+ background-color: #00AF87
+}
+
+div.highlight .-Color[class*=-C37] {
+ color: #00AFAF
+}
+
+div.highlight .-Color[class*=-BGC37] {
+ background-color: #00AFAF
+}
+
+div.highlight .-Color[class*=-C38] {
+ color: #00AFD7
+}
+
+div.highlight .-Color[class*=-BGC38] {
+ background-color: #00AFD7
+}
+
+div.highlight .-Color[class*=-C39] {
+ color: #00AFFF
+}
+
+div.highlight .-Color[class*=-BGC39] {
+ background-color: #00AFFF
+}
+
+div.highlight .-Color[class*=-C40] {
+ color: #00D700
+}
+
+div.highlight .-Color[class*=-BGC40] {
+ background-color: #00D700
+}
+
+div.highlight .-Color[class*=-C41] {
+ color: #00D75F
+}
+
+div.highlight .-Color[class*=-BGC41] {
+ background-color: #00D75F
+}
+
+div.highlight .-Color[class*=-C42] {
+ color: #00D787
+}
+
+div.highlight .-Color[class*=-BGC42] {
+ background-color: #00D787
+}
+
+div.highlight .-Color[class*=-C43] {
+ color: #00D7AF
+}
+
+div.highlight .-Color[class*=-BGC43] {
+ background-color: #00D7AF
+}
+
+div.highlight .-Color[class*=-C44] {
+ color: #00D7D7
+}
+
+div.highlight .-Color[class*=-BGC44] {
+ background-color: #00D7D7
+}
+
+div.highlight .-Color[class*=-C45] {
+ color: #00D7FF
+}
+
+div.highlight .-Color[class*=-BGC45] {
+ background-color: #00D7FF
+}
+
+div.highlight .-Color[class*=-C46] {
+ color: #00FF00
+}
+
+div.highlight .-Color[class*=-BGC46] {
+ background-color: #00FF00
+}
+
+div.highlight .-Color[class*=-C47] {
+ color: #00FF5F
+}
+
+div.highlight .-Color[class*=-BGC47] {
+ background-color: #00FF5F
+}
+
+div.highlight .-Color[class*=-C48] {
+ color: #00FF87
+}
+
+div.highlight .-Color[class*=-BGC48] {
+ background-color: #00FF87
+}
+
+div.highlight .-Color[class*=-C49] {
+ color: #00FFAF
+}
+
+div.highlight .-Color[class*=-BGC49] {
+ background-color: #00FFAF
+}
+
+div.highlight .-Color[class*=-C50] {
+ color: #00FFD7
+}
+
+div.highlight .-Color[class*=-BGC50] {
+ background-color: #00FFD7
+}
+
+div.highlight .-Color[class*=-C51] {
+ color: #00FFFF
+}
+
+div.highlight .-Color[class*=-BGC51] {
+ background-color: #00FFFF
+}
+
+div.highlight .-Color[class*=-C52] {
+ color: #5F0000
+}
+
+div.highlight .-Color[class*=-BGC52] {
+ background-color: #5F0000
+}
+
+div.highlight .-Color[class*=-C53] {
+ color: #5F005F
+}
+
+div.highlight .-Color[class*=-BGC53] {
+ background-color: #5F005F
+}
+
+div.highlight .-Color[class*=-C54] {
+ color: #5F0087
+}
+
+div.highlight .-Color[class*=-BGC54] {
+ background-color: #5F0087
+}
+
+div.highlight .-Color[class*=-C55] {
+ color: #5F00AF
+}
+
+div.highlight .-Color[class*=-BGC55] {
+ background-color: #5F00AF
+}
+
+div.highlight .-Color[class*=-C56] {
+ color: #5F00D7
+}
+
+div.highlight .-Color[class*=-BGC56] {
+ background-color: #5F00D7
+}
+
+div.highlight .-Color[class*=-C57] {
+ color: #5F00FF
+}
+
+div.highlight .-Color[class*=-BGC57] {
+ background-color: #5F00FF
+}
+
+div.highlight .-Color[class*=-C58] {
+ color: #5F5F00
+}
+
+div.highlight .-Color[class*=-BGC58] {
+ background-color: #5F5F00
+}
+
+div.highlight .-Color[class*=-C59] {
+ color: #5F5F5F
+}
+
+div.highlight .-Color[class*=-BGC59] {
+ background-color: #5F5F5F
+}
+
+div.highlight .-Color[class*=-C60] {
+ color: #5F5F87
+}
+
+div.highlight .-Color[class*=-BGC60] {
+ background-color: #5F5F87
+}
+
+div.highlight .-Color[class*=-C61] {
+ color: #5F5FAF
+}
+
+div.highlight .-Color[class*=-BGC61] {
+ background-color: #5F5FAF
+}
+
+div.highlight .-Color[class*=-C62] {
+ color: #5F5FD7
+}
+
+div.highlight .-Color[class*=-BGC62] {
+ background-color: #5F5FD7
+}
+
+div.highlight .-Color[class*=-C63] {
+ color: #5F5FFF
+}
+
+div.highlight .-Color[class*=-BGC63] {
+ background-color: #5F5FFF
+}
+
+div.highlight .-Color[class*=-C64] {
+ color: #5F8700
+}
+
+div.highlight .-Color[class*=-BGC64] {
+ background-color: #5F8700
+}
+
+div.highlight .-Color[class*=-C65] {
+ color: #5F875F
+}
+
+div.highlight .-Color[class*=-BGC65] {
+ background-color: #5F875F
+}
+
+div.highlight .-Color[class*=-C66] {
+ color: #5F8787
+}
+
+div.highlight .-Color[class*=-BGC66] {
+ background-color: #5F8787
+}
+
+div.highlight .-Color[class*=-C67] {
+ color: #5F87AF
+}
+
+div.highlight .-Color[class*=-BGC67] {
+ background-color: #5F87AF
+}
+
+div.highlight .-Color[class*=-C68] {
+ color: #5F87D7
+}
+
+div.highlight .-Color[class*=-BGC68] {
+ background-color: #5F87D7
+}
+
+div.highlight .-Color[class*=-C69] {
+ color: #5F87FF
+}
+
+div.highlight .-Color[class*=-BGC69] {
+ background-color: #5F87FF
+}
+
+div.highlight .-Color[class*=-C70] {
+ color: #5FAF00
+}
+
+div.highlight .-Color[class*=-BGC70] {
+ background-color: #5FAF00
+}
+
+div.highlight .-Color[class*=-C71] {
+ color: #5FAF5F
+}
+
+div.highlight .-Color[class*=-BGC71] {
+ background-color: #5FAF5F
+}
+
+div.highlight .-Color[class*=-C72] {
+ color: #5FAF87
+}
+
+div.highlight .-Color[class*=-BGC72] {
+ background-color: #5FAF87
+}
+
+div.highlight .-Color[class*=-C73] {
+ color: #5FAFAF
+}
+
+div.highlight .-Color[class*=-BGC73] {
+ background-color: #5FAFAF
+}
+
+div.highlight .-Color[class*=-C74] {
+ color: #5FAFD7
+}
+
+div.highlight .-Color[class*=-BGC74] {
+ background-color: #5FAFD7
+}
+
+div.highlight .-Color[class*=-C75] {
+ color: #5FAFFF
+}
+
+div.highlight .-Color[class*=-BGC75] {
+ background-color: #5FAFFF
+}
+
+div.highlight .-Color[class*=-C76] {
+ color: #5FD700
+}
+
+div.highlight .-Color[class*=-BGC76] {
+ background-color: #5FD700
+}
+
+div.highlight .-Color[class*=-C77] {
+ color: #5FD75F
+}
+
+div.highlight .-Color[class*=-BGC77] {
+ background-color: #5FD75F
+}
+
+div.highlight .-Color[class*=-C78] {
+ color: #5FD787
+}
+
+div.highlight .-Color[class*=-BGC78] {
+ background-color: #5FD787
+}
+
+div.highlight .-Color[class*=-C79] {
+ color: #5FD7AF
+}
+
+div.highlight .-Color[class*=-BGC79] {
+ background-color: #5FD7AF
+}
+
+div.highlight .-Color[class*=-C80] {
+ color: #5FD7D7
+}
+
+div.highlight .-Color[class*=-BGC80] {
+ background-color: #5FD7D7
+}
+
+div.highlight .-Color[class*=-C81] {
+ color: #5FD7FF
+}
+
+div.highlight .-Color[class*=-BGC81] {
+ background-color: #5FD7FF
+}
+
+div.highlight .-Color[class*=-C82] {
+ color: #5FFF00
+}
+
+div.highlight .-Color[class*=-BGC82] {
+ background-color: #5FFF00
+}
+
+div.highlight .-Color[class*=-C83] {
+ color: #5FFF5F
+}
+
+div.highlight .-Color[class*=-BGC83] {
+ background-color: #5FFF5F
+}
+
+div.highlight .-Color[class*=-C84] {
+ color: #5FFF87
+}
+
+div.highlight .-Color[class*=-BGC84] {
+ background-color: #5FFF87
+}
+
+div.highlight .-Color[class*=-C85] {
+ color: #5FFFAF
+}
+
+div.highlight .-Color[class*=-BGC85] {
+ background-color: #5FFFAF
+}
+
+div.highlight .-Color[class*=-C86] {
+ color: #5FFFD7
+}
+
+div.highlight .-Color[class*=-BGC86] {
+ background-color: #5FFFD7
+}
+
+div.highlight .-Color[class*=-C87] {
+ color: #5FFFFF
+}
+
+div.highlight .-Color[class*=-BGC87] {
+ background-color: #5FFFFF
+}
+
+div.highlight .-Color[class*=-C88] {
+ color: #870000
+}
+
+div.highlight .-Color[class*=-BGC88] {
+ background-color: #870000
+}
+
+div.highlight .-Color[class*=-C89] {
+ color: #87005F
+}
+
+div.highlight .-Color[class*=-BGC89] {
+ background-color: #87005F
+}
+
+div.highlight .-Color[class*=-C90] {
+ color: #870087
+}
+
+div.highlight .-Color[class*=-BGC90] {
+ background-color: #870087
+}
+
+div.highlight .-Color[class*=-C91] {
+ color: #8700AF
+}
+
+div.highlight .-Color[class*=-BGC91] {
+ background-color: #8700AF
+}
+
+div.highlight .-Color[class*=-C92] {
+ color: #8700D7
+}
+
+div.highlight .-Color[class*=-BGC92] {
+ background-color: #8700D7
+}
+
+div.highlight .-Color[class*=-C93] {
+ color: #8700FF
+}
+
+div.highlight .-Color[class*=-BGC93] {
+ background-color: #8700FF
+}
+
+div.highlight .-Color[class*=-C94] {
+ color: #875F00
+}
+
+div.highlight .-Color[class*=-BGC94] {
+ background-color: #875F00
+}
+
+div.highlight .-Color[class*=-C95] {
+ color: #875F5F
+}
+
+div.highlight .-Color[class*=-BGC95] {
+ background-color: #875F5F
+}
+
+div.highlight .-Color[class*=-C96] {
+ color: #875F87
+}
+
+div.highlight .-Color[class*=-BGC96] {
+ background-color: #875F87
+}
+
+div.highlight .-Color[class*=-C97] {
+ color: #875FAF
+}
+
+div.highlight .-Color[class*=-BGC97] {
+ background-color: #875FAF
+}
+
+div.highlight .-Color[class*=-C98] {
+ color: #875FD7
+}
+
+div.highlight .-Color[class*=-BGC98] {
+ background-color: #875FD7
+}
+
+div.highlight .-Color[class*=-C99] {
+ color: #875FFF
+}
+
+div.highlight .-Color[class*=-BGC99] {
+ background-color: #875FFF
+}
+
+div.highlight .-Color[class*=-C100] {
+ color: #878700
+}
+
+div.highlight .-Color[class*=-BGC100] {
+ background-color: #878700
+}
+
+div.highlight .-Color[class*=-C101] {
+ color: #87875F
+}
+
+div.highlight .-Color[class*=-BGC101] {
+ background-color: #87875F
+}
+
+div.highlight .-Color[class*=-C102] {
+ color: #878787
+}
+
+div.highlight .-Color[class*=-BGC102] {
+ background-color: #878787
+}
+
+div.highlight .-Color[class*=-C103] {
+ color: #8787AF
+}
+
+div.highlight .-Color[class*=-BGC103] {
+ background-color: #8787AF
+}
+
+div.highlight .-Color[class*=-C104] {
+ color: #8787D7
+}
+
+div.highlight .-Color[class*=-BGC104] {
+ background-color: #8787D7
+}
+
+div.highlight .-Color[class*=-C105] {
+ color: #8787FF
+}
+
+div.highlight .-Color[class*=-BGC105] {
+ background-color: #8787FF
+}
+
+div.highlight .-Color[class*=-C106] {
+ color: #87AF00
+}
+
+div.highlight .-Color[class*=-BGC106] {
+ background-color: #87AF00
+}
+
+div.highlight .-Color[class*=-C107] {
+ color: #87AF5F
+}
+
+div.highlight .-Color[class*=-BGC107] {
+ background-color: #87AF5F
+}
+
+div.highlight .-Color[class*=-C108] {
+ color: #87AF87
+}
+
+div.highlight .-Color[class*=-BGC108] {
+ background-color: #87AF87
+}
+
+div.highlight .-Color[class*=-C109] {
+ color: #87AFAF
+}
+
+div.highlight .-Color[class*=-BGC109] {
+ background-color: #87AFAF
+}
+
+div.highlight .-Color[class*=-C110] {
+ color: #87AFD7
+}
+
+div.highlight .-Color[class*=-BGC110] {
+ background-color: #87AFD7
+}
+
+div.highlight .-Color[class*=-C111] {
+ color: #87AFFF
+}
+
+div.highlight .-Color[class*=-BGC111] {
+ background-color: #87AFFF
+}
+
+div.highlight .-Color[class*=-C112] {
+ color: #87D700
+}
+
+div.highlight .-Color[class*=-BGC112] {
+ background-color: #87D700
+}
+
+div.highlight .-Color[class*=-C113] {
+ color: #87D75F
+}
+
+div.highlight .-Color[class*=-BGC113] {
+ background-color: #87D75F
+}
+
+div.highlight .-Color[class*=-C114] {
+ color: #87D787
+}
+
+div.highlight .-Color[class*=-BGC114] {
+ background-color: #87D787
+}
+
+div.highlight .-Color[class*=-C115] {
+ color: #87D7AF
+}
+
+div.highlight .-Color[class*=-BGC115] {
+ background-color: #87D7AF
+}
+
+div.highlight .-Color[class*=-C116] {
+ color: #87D7D7
+}
+
+div.highlight .-Color[class*=-BGC116] {
+ background-color: #87D7D7
+}
+
+div.highlight .-Color[class*=-C117] {
+ color: #87D7FF
+}
+
+div.highlight .-Color[class*=-BGC117] {
+ background-color: #87D7FF
+}
+
+div.highlight .-Color[class*=-C118] {
+ color: #87FF00
+}
+
+div.highlight .-Color[class*=-BGC118] {
+ background-color: #87FF00
+}
+
+div.highlight .-Color[class*=-C119] {
+ color: #87FF5F
+}
+
+div.highlight .-Color[class*=-BGC119] {
+ background-color: #87FF5F
+}
+
+div.highlight .-Color[class*=-C120] {
+ color: #87FF87
+}
+
+div.highlight .-Color[class*=-BGC120] {
+ background-color: #87FF87
+}
+
+div.highlight .-Color[class*=-C121] {
+ color: #87FFAF
+}
+
+div.highlight .-Color[class*=-BGC121] {
+ background-color: #87FFAF
+}
+
+div.highlight .-Color[class*=-C122] {
+ color: #87FFD7
+}
+
+div.highlight .-Color[class*=-BGC122] {
+ background-color: #87FFD7
+}
+
+div.highlight .-Color[class*=-C123] {
+ color: #87FFFF
+}
+
+div.highlight .-Color[class*=-BGC123] {
+ background-color: #87FFFF
+}
+
+div.highlight .-Color[class*=-C124] {
+ color: #AF0000
+}
+
+div.highlight .-Color[class*=-BGC124] {
+ background-color: #AF0000
+}
+
+div.highlight .-Color[class*=-C125] {
+ color: #AF005F
+}
+
+div.highlight .-Color[class*=-BGC125] {
+ background-color: #AF005F
+}
+
+div.highlight .-Color[class*=-C126] {
+ color: #AF0087
+}
+
+div.highlight .-Color[class*=-BGC126] {
+ background-color: #AF0087
+}
+
+div.highlight .-Color[class*=-C127] {
+ color: #AF00AF
+}
+
+div.highlight .-Color[class*=-BGC127] {
+ background-color: #AF00AF
+}
+
+div.highlight .-Color[class*=-C128] {
+ color: #AF00D7
+}
+
+div.highlight .-Color[class*=-BGC128] {
+ background-color: #AF00D7
+}
+
+div.highlight .-Color[class*=-C129] {
+ color: #AF00FF
+}
+
+div.highlight .-Color[class*=-BGC129] {
+ background-color: #AF00FF
+}
+
+div.highlight .-Color[class*=-C130] {
+ color: #AF5F00
+}
+
+div.highlight .-Color[class*=-BGC130] {
+ background-color: #AF5F00
+}
+
+div.highlight .-Color[class*=-C131] {
+ color: #AF5F5F
+}
+
+div.highlight .-Color[class*=-BGC131] {
+ background-color: #AF5F5F
+}
+
+div.highlight .-Color[class*=-C132] {
+ color: #AF5F87
+}
+
+div.highlight .-Color[class*=-BGC132] {
+ background-color: #AF5F87
+}
+
+div.highlight .-Color[class*=-C133] {
+ color: #AF5FAF
+}
+
+div.highlight .-Color[class*=-BGC133] {
+ background-color: #AF5FAF
+}
+
+div.highlight .-Color[class*=-C134] {
+ color: #AF5FD7
+}
+
+div.highlight .-Color[class*=-BGC134] {
+ background-color: #AF5FD7
+}
+
+div.highlight .-Color[class*=-C135] {
+ color: #AF5FFF
+}
+
+div.highlight .-Color[class*=-BGC135] {
+ background-color: #AF5FFF
+}
+
+div.highlight .-Color[class*=-C136] {
+ color: #AF8700
+}
+
+div.highlight .-Color[class*=-BGC136] {
+ background-color: #AF8700
+}
+
+div.highlight .-Color[class*=-C137] {
+ color: #AF875F
+}
+
+div.highlight .-Color[class*=-BGC137] {
+ background-color: #AF875F
+}
+
+div.highlight .-Color[class*=-C138] {
+ color: #AF8787
+}
+
+div.highlight .-Color[class*=-BGC138] {
+ background-color: #AF8787
+}
+
+div.highlight .-Color[class*=-C139] {
+ color: #AF87AF
+}
+
+div.highlight .-Color[class*=-BGC139] {
+ background-color: #AF87AF
+}
+
+div.highlight .-Color[class*=-C140] {
+ color: #AF87D7
+}
+
+div.highlight .-Color[class*=-BGC140] {
+ background-color: #AF87D7
+}
+
+div.highlight .-Color[class*=-C141] {
+ color: #AF87FF
+}
+
+div.highlight .-Color[class*=-BGC141] {
+ background-color: #AF87FF
+}
+
+div.highlight .-Color[class*=-C142] {
+ color: #AFAF00
+}
+
+div.highlight .-Color[class*=-BGC142] {
+ background-color: #AFAF00
+}
+
+div.highlight .-Color[class*=-C143] {
+ color: #AFAF5F
+}
+
+div.highlight .-Color[class*=-BGC143] {
+ background-color: #AFAF5F
+}
+
+div.highlight .-Color[class*=-C144] {
+ color: #AFAF87
+}
+
+div.highlight .-Color[class*=-BGC144] {
+ background-color: #AFAF87
+}
+
+div.highlight .-Color[class*=-C145] {
+ color: #AFAFAF
+}
+
+div.highlight .-Color[class*=-BGC145] {
+ background-color: #AFAFAF
+}
+
+div.highlight .-Color[class*=-C146] {
+ color: #AFAFD7
+}
+
+div.highlight .-Color[class*=-BGC146] {
+ background-color: #AFAFD7
+}
+
+div.highlight .-Color[class*=-C147] {
+ color: #AFAFFF
+}
+
+div.highlight .-Color[class*=-BGC147] {
+ background-color: #AFAFFF
+}
+
+div.highlight .-Color[class*=-C148] {
+ color: #AFD700
+}
+
+div.highlight .-Color[class*=-BGC148] {
+ background-color: #AFD700
+}
+
+div.highlight .-Color[class*=-C149] {
+ color: #AFD75F
+}
+
+div.highlight .-Color[class*=-BGC149] {
+ background-color: #AFD75F
+}
+
+div.highlight .-Color[class*=-C150] {
+ color: #AFD787
+}
+
+div.highlight .-Color[class*=-BGC150] {
+ background-color: #AFD787
+}
+
+div.highlight .-Color[class*=-C151] {
+ color: #AFD7AF
+}
+
+div.highlight .-Color[class*=-BGC151] {
+ background-color: #AFD7AF
+}
+
+div.highlight .-Color[class*=-C152] {
+ color: #AFD7D7
+}
+
+div.highlight .-Color[class*=-BGC152] {
+ background-color: #AFD7D7
+}
+
+div.highlight .-Color[class*=-C153] {
+ color: #AFD7FF
+}
+
+div.highlight .-Color[class*=-BGC153] {
+ background-color: #AFD7FF
+}
+
+div.highlight .-Color[class*=-C154] {
+ color: #AFFF00
+}
+
+div.highlight .-Color[class*=-BGC154] {
+ background-color: #AFFF00
+}
+
+div.highlight .-Color[class*=-C155] {
+ color: #AFFF5F
+}
+
+div.highlight .-Color[class*=-BGC155] {
+ background-color: #AFFF5F
+}
+
+div.highlight .-Color[class*=-C156] {
+ color: #AFFF87
+}
+
+div.highlight .-Color[class*=-BGC156] {
+ background-color: #AFFF87
+}
+
+div.highlight .-Color[class*=-C157] {
+ color: #AFFFAF
+}
+
+div.highlight .-Color[class*=-BGC157] {
+ background-color: #AFFFAF
+}
+
+div.highlight .-Color[class*=-C158] {
+ color: #AFFFD7
+}
+
+div.highlight .-Color[class*=-BGC158] {
+ background-color: #AFFFD7
+}
+
+div.highlight .-Color[class*=-C159] {
+ color: #AFFFFF
+}
+
+div.highlight .-Color[class*=-BGC159] {
+ background-color: #AFFFFF
+}
+
+div.highlight .-Color[class*=-C160] {
+ color: #D70000
+}
+
+div.highlight .-Color[class*=-BGC160] {
+ background-color: #D70000
+}
+
+div.highlight .-Color[class*=-C161] {
+ color: #D7005F
+}
+
+div.highlight .-Color[class*=-BGC161] {
+ background-color: #D7005F
+}
+
+div.highlight .-Color[class*=-C162] {
+ color: #D70087
+}
+
+div.highlight .-Color[class*=-BGC162] {
+ background-color: #D70087
+}
+
+div.highlight .-Color[class*=-C163] {
+ color: #D700AF
+}
+
+div.highlight .-Color[class*=-BGC163] {
+ background-color: #D700AF
+}
+
+div.highlight .-Color[class*=-C164] {
+ color: #D700D7
+}
+
+div.highlight .-Color[class*=-BGC164] {
+ background-color: #D700D7
+}
+
+div.highlight .-Color[class*=-C165] {
+ color: #D700FF
+}
+
+div.highlight .-Color[class*=-BGC165] {
+ background-color: #D700FF
+}
+
+div.highlight .-Color[class*=-C166] {
+ color: #D75F00
+}
+
+div.highlight .-Color[class*=-BGC166] {
+ background-color: #D75F00
+}
+
+div.highlight .-Color[class*=-C167] {
+ color: #D75F5F
+}
+
+div.highlight .-Color[class*=-BGC167] {
+ background-color: #D75F5F
+}
+
+div.highlight .-Color[class*=-C168] {
+ color: #D75F87
+}
+
+div.highlight .-Color[class*=-BGC168] {
+ background-color: #D75F87
+}
+
+div.highlight .-Color[class*=-C169] {
+ color: #D75FAF
+}
+
+div.highlight .-Color[class*=-BGC169] {
+ background-color: #D75FAF
+}
+
+div.highlight .-Color[class*=-C170] {
+ color: #D75FD7
+}
+
+div.highlight .-Color[class*=-BGC170] {
+ background-color: #D75FD7
+}
+
+div.highlight .-Color[class*=-C171] {
+ color: #D75FFF
+}
+
+div.highlight .-Color[class*=-BGC171] {
+ background-color: #D75FFF
+}
+
+div.highlight .-Color[class*=-C172] {
+ color: #D78700
+}
+
+div.highlight .-Color[class*=-BGC172] {
+ background-color: #D78700
+}
+
+div.highlight .-Color[class*=-C173] {
+ color: #D7875F
+}
+
+div.highlight .-Color[class*=-BGC173] {
+ background-color: #D7875F
+}
+
+div.highlight .-Color[class*=-C174] {
+ color: #D78787
+}
+
+div.highlight .-Color[class*=-BGC174] {
+ background-color: #D78787
+}
+
+div.highlight .-Color[class*=-C175] {
+ color: #D787AF
+}
+
+div.highlight .-Color[class*=-BGC175] {
+ background-color: #D787AF
+}
+
+div.highlight .-Color[class*=-C176] {
+ color: #D787D7
+}
+
+div.highlight .-Color[class*=-BGC176] {
+ background-color: #D787D7
+}
+
+div.highlight .-Color[class*=-C177] {
+ color: #D787FF
+}
+
+div.highlight .-Color[class*=-BGC177] {
+ background-color: #D787FF
+}
+
+div.highlight .-Color[class*=-C178] {
+ color: #D7AF00
+}
+
+div.highlight .-Color[class*=-BGC178] {
+ background-color: #D7AF00
+}
+
+div.highlight .-Color[class*=-C179] {
+ color: #D7AF5F
+}
+
+div.highlight .-Color[class*=-BGC179] {
+ background-color: #D7AF5F
+}
+
+div.highlight .-Color[class*=-C180] {
+ color: #D7AF87
+}
+
+div.highlight .-Color[class*=-BGC180] {
+ background-color: #D7AF87
+}
+
+div.highlight .-Color[class*=-C181] {
+ color: #D7AFAF
+}
+
+div.highlight .-Color[class*=-BGC181] {
+ background-color: #D7AFAF
+}
+
+div.highlight .-Color[class*=-C182] {
+ color: #D7AFD7
+}
+
+div.highlight .-Color[class*=-BGC182] {
+ background-color: #D7AFD7
+}
+
+div.highlight .-Color[class*=-C183] {
+ color: #D7AFFF
+}
+
+div.highlight .-Color[class*=-BGC183] {
+ background-color: #D7AFFF
+}
+
+div.highlight .-Color[class*=-C184] {
+ color: #D7D700
+}
+
+div.highlight .-Color[class*=-BGC184] {
+ background-color: #D7D700
+}
+
+div.highlight .-Color[class*=-C185] {
+ color: #D7D75F
+}
+
+div.highlight .-Color[class*=-BGC185] {
+ background-color: #D7D75F
+}
+
+div.highlight .-Color[class*=-C186] {
+ color: #D7D787
+}
+
+div.highlight .-Color[class*=-BGC186] {
+ background-color: #D7D787
+}
+
+div.highlight .-Color[class*=-C187] {
+ color: #D7D7AF
+}
+
+div.highlight .-Color[class*=-BGC187] {
+ background-color: #D7D7AF
+}
+
+div.highlight .-Color[class*=-C188] {
+ color: #D7D7D7
+}
+
+div.highlight .-Color[class*=-BGC188] {
+ background-color: #D7D7D7
+}
+
+div.highlight .-Color[class*=-C189] {
+ color: #D7D7FF
+}
+
+div.highlight .-Color[class*=-BGC189] {
+ background-color: #D7D7FF
+}
+
+div.highlight .-Color[class*=-C190] {
+ color: #D7FF00
+}
+
+div.highlight .-Color[class*=-BGC190] {
+ background-color: #D7FF00
+}
+
+div.highlight .-Color[class*=-C191] {
+ color: #D7FF5F
+}
+
+div.highlight .-Color[class*=-BGC191] {
+ background-color: #D7FF5F
+}
+
+div.highlight .-Color[class*=-C192] {
+ color: #D7FF87
+}
+
+div.highlight .-Color[class*=-BGC192] {
+ background-color: #D7FF87
+}
+
+div.highlight .-Color[class*=-C193] {
+ color: #D7FFAF
+}
+
+div.highlight .-Color[class*=-BGC193] {
+ background-color: #D7FFAF
+}
+
+div.highlight .-Color[class*=-C194] {
+ color: #D7FFD7
+}
+
+div.highlight .-Color[class*=-BGC194] {
+ background-color: #D7FFD7
+}
+
+div.highlight .-Color[class*=-C195] {
+ color: #D7FFFF
+}
+
+div.highlight .-Color[class*=-BGC195] {
+ background-color: #D7FFFF
+}
+
+div.highlight .-Color[class*=-C196] {
+ color: #FF0000
+}
+
+div.highlight .-Color[class*=-BGC196] {
+ background-color: #FF0000
+}
+
+div.highlight .-Color[class*=-C197] {
+ color: #FF005F
+}
+
+div.highlight .-Color[class*=-BGC197] {
+ background-color: #FF005F
+}
+
+div.highlight .-Color[class*=-C198] {
+ color: #FF0087
+}
+
+div.highlight .-Color[class*=-BGC198] {
+ background-color: #FF0087
+}
+
+div.highlight .-Color[class*=-C199] {
+ color: #FF00AF
+}
+
+div.highlight .-Color[class*=-BGC199] {
+ background-color: #FF00AF
+}
+
+div.highlight .-Color[class*=-C200] {
+ color: #FF00D7
+}
+
+div.highlight .-Color[class*=-BGC200] {
+ background-color: #FF00D7
+}
+
+div.highlight .-Color[class*=-C201] {
+ color: #FF00FF
+}
+
+div.highlight .-Color[class*=-BGC201] {
+ background-color: #FF00FF
+}
+
+div.highlight .-Color[class*=-C202] {
+ color: #FF5F00
+}
+
+div.highlight .-Color[class*=-BGC202] {
+ background-color: #FF5F00
+}
+
+div.highlight .-Color[class*=-C203] {
+ color: #FF5F5F
+}
+
+div.highlight .-Color[class*=-BGC203] {
+ background-color: #FF5F5F
+}
+
+div.highlight .-Color[class*=-C204] {
+ color: #FF5F87
+}
+
+div.highlight .-Color[class*=-BGC204] {
+ background-color: #FF5F87
+}
+
+div.highlight .-Color[class*=-C205] {
+ color: #FF5FAF
+}
+
+div.highlight .-Color[class*=-BGC205] {
+ background-color: #FF5FAF
+}
+
+div.highlight .-Color[class*=-C206] {
+ color: #FF5FD7
+}
+
+div.highlight .-Color[class*=-BGC206] {
+ background-color: #FF5FD7
+}
+
+div.highlight .-Color[class*=-C207] {
+ color: #FF5FFF
+}
+
+div.highlight .-Color[class*=-BGC207] {
+ background-color: #FF5FFF
+}
+
+div.highlight .-Color[class*=-C208] {
+ color: #FF8700
+}
+
+div.highlight .-Color[class*=-BGC208] {
+ background-color: #FF8700
+}
+
+div.highlight .-Color[class*=-C209] {
+ color: #FF875F
+}
+
+div.highlight .-Color[class*=-BGC209] {
+ background-color: #FF875F
+}
+
+div.highlight .-Color[class*=-C210] {
+ color: #FF8787
+}
+
+div.highlight .-Color[class*=-BGC210] {
+ background-color: #FF8787
+}
+
+div.highlight .-Color[class*=-C211] {
+ color: #FF87AF
+}
+
+div.highlight .-Color[class*=-BGC211] {
+ background-color: #FF87AF
+}
+
+div.highlight .-Color[class*=-C212] {
+ color: #FF87D7
+}
+
+div.highlight .-Color[class*=-BGC212] {
+ background-color: #FF87D7
+}
+
+div.highlight .-Color[class*=-C213] {
+ color: #FF87FF
+}
+
+div.highlight .-Color[class*=-BGC213] {
+ background-color: #FF87FF
+}
+
+div.highlight .-Color[class*=-C214] {
+ color: #FFAF00
+}
+
+div.highlight .-Color[class*=-BGC214] {
+ background-color: #FFAF00
+}
+
+div.highlight .-Color[class*=-C215] {
+ color: #FFAF5F
+}
+
+div.highlight .-Color[class*=-BGC215] {
+ background-color: #FFAF5F
+}
+
+div.highlight .-Color[class*=-C216] {
+ color: #FFAF87
+}
+
+div.highlight .-Color[class*=-BGC216] {
+ background-color: #FFAF87
+}
+
+div.highlight .-Color[class*=-C217] {
+ color: #FFAFAF
+}
+
+div.highlight .-Color[class*=-BGC217] {
+ background-color: #FFAFAF
+}
+
+div.highlight .-Color[class*=-C218] {
+ color: #FFAFD7
+}
+
+div.highlight .-Color[class*=-BGC218] {
+ background-color: #FFAFD7
+}
+
+div.highlight .-Color[class*=-C219] {
+ color: #FFAFFF
+}
+
+div.highlight .-Color[class*=-BGC219] {
+ background-color: #FFAFFF
+}
+
+div.highlight .-Color[class*=-C220] {
+ color: #FFD700
+}
+
+div.highlight .-Color[class*=-BGC220] {
+ background-color: #FFD700
+}
+
+div.highlight .-Color[class*=-C221] {
+ color: #FFD75F
+}
+
+div.highlight .-Color[class*=-BGC221] {
+ background-color: #FFD75F
+}
+
+div.highlight .-Color[class*=-C222] {
+ color: #FFD787
+}
+
+div.highlight .-Color[class*=-BGC222] {
+ background-color: #FFD787
+}
+
+div.highlight .-Color[class*=-C223] {
+ color: #FFD7AF
+}
+
+div.highlight .-Color[class*=-BGC223] {
+ background-color: #FFD7AF
+}
+
+div.highlight .-Color[class*=-C224] {
+ color: #FFD7D7
+}
+
+div.highlight .-Color[class*=-BGC224] {
+ background-color: #FFD7D7
+}
+
+div.highlight .-Color[class*=-C225] {
+ color: #FFD7FF
+}
+
+div.highlight .-Color[class*=-BGC225] {
+ background-color: #FFD7FF
+}
+
+div.highlight .-Color[class*=-C226] {
+ color: #FFFF00
+}
+
+div.highlight .-Color[class*=-BGC226] {
+ background-color: #FFFF00
+}
+
+div.highlight .-Color[class*=-C227] {
+ color: #FFFF5F
+}
+
+div.highlight .-Color[class*=-BGC227] {
+ background-color: #FFFF5F
+}
+
+div.highlight .-Color[class*=-C228] {
+ color: #FFFF87
+}
+
+div.highlight .-Color[class*=-BGC228] {
+ background-color: #FFFF87
+}
+
+div.highlight .-Color[class*=-C229] {
+ color: #FFFFAF
+}
+
+div.highlight .-Color[class*=-BGC229] {
+ background-color: #FFFFAF
+}
+
+div.highlight .-Color[class*=-C230] {
+ color: #FFFFD7
+}
+
+div.highlight .-Color[class*=-BGC230] {
+ background-color: #FFFFD7
+}
+
+div.highlight .-Color[class*=-C231] {
+ color: #FFFFFF
+}
+
+div.highlight .-Color[class*=-BGC231] {
+ background-color: #FFFFFF
+}
+
+div.highlight .-Color[class*=-C232] {
+ color: #080808
+}
+
+div.highlight .-Color[class*=-BGC232] {
+ background-color: #080808
+}
+
+div.highlight .-Color[class*=-C233] {
+ color: #121212
+}
+
+div.highlight .-Color[class*=-BGC233] {
+ background-color: #121212
+}
+
+div.highlight .-Color[class*=-C234] {
+ color: #1C1C1C
+}
+
+div.highlight .-Color[class*=-BGC234] {
+ background-color: #1C1C1C
+}
+
+div.highlight .-Color[class*=-C235] {
+ color: #262626
+}
+
+div.highlight .-Color[class*=-BGC235] {
+ background-color: #262626
+}
+
+div.highlight .-Color[class*=-C236] {
+ color: #303030
+}
+
+div.highlight .-Color[class*=-BGC236] {
+ background-color: #303030
+}
+
+div.highlight .-Color[class*=-C237] {
+ color: #3A3A3A
+}
+
+div.highlight .-Color[class*=-BGC237] {
+ background-color: #3A3A3A
+}
+
+div.highlight .-Color[class*=-C238] {
+ color: #444444
+}
+
+div.highlight .-Color[class*=-BGC238] {
+ background-color: #444444
+}
+
+div.highlight .-Color[class*=-C239] {
+ color: #4E4E4E
+}
+
+div.highlight .-Color[class*=-BGC239] {
+ background-color: #4E4E4E
+}
+
+div.highlight .-Color[class*=-C240] {
+ color: #585858
+}
+
+div.highlight .-Color[class*=-BGC240] {
+ background-color: #585858
+}
+
+div.highlight .-Color[class*=-C241] {
+ color: #626262
+}
+
+div.highlight .-Color[class*=-BGC241] {
+ background-color: #626262
+}
+
+div.highlight .-Color[class*=-C242] {
+ color: #6C6C6C
+}
+
+div.highlight .-Color[class*=-BGC242] {
+ background-color: #6C6C6C
+}
+
+div.highlight .-Color[class*=-C243] {
+ color: #767676
+}
+
+div.highlight .-Color[class*=-BGC243] {
+ background-color: #767676
+}
+
+div.highlight .-Color[class*=-C244] {
+ color: #808080
+}
+
+div.highlight .-Color[class*=-BGC244] {
+ background-color: #808080
+}
+
+div.highlight .-Color[class*=-C245] {
+ color: #8A8A8A
+}
+
+div.highlight .-Color[class*=-BGC245] {
+ background-color: #8A8A8A
+}
+
+div.highlight .-Color[class*=-C246] {
+ color: #949494
+}
+
+div.highlight .-Color[class*=-BGC246] {
+ background-color: #949494
+}
+
+div.highlight .-Color[class*=-C247] {
+ color: #9E9E9E
+}
+
+div.highlight .-Color[class*=-BGC247] {
+ background-color: #9E9E9E
+}
+
+div.highlight .-Color[class*=-C248] {
+ color: #A8A8A8
+}
+
+div.highlight .-Color[class*=-BGC248] {
+ background-color: #A8A8A8
+}
+
+div.highlight .-Color[class*=-C249] {
+ color: #B2B2B2
+}
+
+div.highlight .-Color[class*=-BGC249] {
+ background-color: #B2B2B2
+}
+
+div.highlight .-Color[class*=-C250] {
+ color: #BCBCBC
+}
+
+div.highlight .-Color[class*=-BGC250] {
+ background-color: #BCBCBC
+}
+
+div.highlight .-Color[class*=-C251] {
+ color: #C6C6C6
+}
+
+div.highlight .-Color[class*=-BGC251] {
+ background-color: #C6C6C6
+}
+
+div.highlight .-Color[class*=-C252] {
+ color: #D0D0D0
+}
+
+div.highlight .-Color[class*=-BGC252] {
+ background-color: #D0D0D0
+}
+
+div.highlight .-Color[class*=-C253] {
+ color: #DADADA
+}
+
+div.highlight .-Color[class*=-BGC253] {
+ background-color: #DADADA
+}
+
+div.highlight .-Color[class*=-C254] {
+ color: #E4E4E4
+}
+
+div.highlight .-Color[class*=-BGC254] {
+ background-color: #E4E4E4
+}
+
+div.highlight .-Color[class*=-C255] {
+ color: #EEEEEE
+}
+
+div.highlight .-Color[class*=-BGC255] {
+ background-color: #EEEEEE
+}
diff --git a/_static/plus.png b/_static/plus.png
new file mode 100644
index 0000000..7107cec
Binary files /dev/null and b/_static/plus.png differ
diff --git a/_static/pygments.css b/_static/pygments.css
new file mode 100644
index 0000000..6f8b210
--- /dev/null
+++ b/_static/pygments.css
@@ -0,0 +1,75 @@
+pre { line-height: 125%; }
+td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
+span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
+td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
+span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
+.highlight .hll { background-color: #ffffcc }
+.highlight { background: #f8f8f8; }
+.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */
+.highlight .err { border: 1px solid #F00 } /* Error */
+.highlight .k { color: #008000; font-weight: bold } /* Keyword */
+.highlight .o { color: #666 } /* Operator */
+.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */
+.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */
+.highlight .cp { color: #9C6500 } /* Comment.Preproc */
+.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */
+.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */
+.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */
+.highlight .gd { color: #A00000 } /* Generic.Deleted */
+.highlight .ge { font-style: italic } /* Generic.Emph */
+.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
+.highlight .gr { color: #E40000 } /* Generic.Error */
+.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.highlight .gi { color: #008400 } /* Generic.Inserted */
+.highlight .go { color: #717171 } /* Generic.Output */
+.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
+.highlight .gs { font-weight: bold } /* Generic.Strong */
+.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.highlight .gt { color: #04D } /* Generic.Traceback */
+.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
+.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
+.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
+.highlight .kp { color: #008000 } /* Keyword.Pseudo */
+.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
+.highlight .kt { color: #B00040 } /* Keyword.Type */
+.highlight .m { color: #666 } /* Literal.Number */
+.highlight .s { color: #BA2121 } /* Literal.String */
+.highlight .na { color: #687822 } /* Name.Attribute */
+.highlight .nb { color: #008000 } /* Name.Builtin */
+.highlight .nc { color: #00F; font-weight: bold } /* Name.Class */
+.highlight .no { color: #800 } /* Name.Constant */
+.highlight .nd { color: #A2F } /* Name.Decorator */
+.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */
+.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */
+.highlight .nf { color: #00F } /* Name.Function */
+.highlight .nl { color: #767600 } /* Name.Label */
+.highlight .nn { color: #00F; font-weight: bold } /* Name.Namespace */
+.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */
+.highlight .nv { color: #19177C } /* Name.Variable */
+.highlight .ow { color: #A2F; font-weight: bold } /* Operator.Word */
+.highlight .w { color: #BBB } /* Text.Whitespace */
+.highlight .mb { color: #666 } /* Literal.Number.Bin */
+.highlight .mf { color: #666 } /* Literal.Number.Float */
+.highlight .mh { color: #666 } /* Literal.Number.Hex */
+.highlight .mi { color: #666 } /* Literal.Number.Integer */
+.highlight .mo { color: #666 } /* Literal.Number.Oct */
+.highlight .sa { color: #BA2121 } /* Literal.String.Affix */
+.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */
+.highlight .sc { color: #BA2121 } /* Literal.String.Char */
+.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */
+.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
+.highlight .s2 { color: #BA2121 } /* Literal.String.Double */
+.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */
+.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */
+.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */
+.highlight .sx { color: #008000 } /* Literal.String.Other */
+.highlight .sr { color: #A45A77 } /* Literal.String.Regex */
+.highlight .s1 { color: #BA2121 } /* Literal.String.Single */
+.highlight .ss { color: #19177C } /* Literal.String.Symbol */
+.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */
+.highlight .fm { color: #00F } /* Name.Function.Magic */
+.highlight .vc { color: #19177C } /* Name.Variable.Class */
+.highlight .vg { color: #19177C } /* Name.Variable.Global */
+.highlight .vi { color: #19177C } /* Name.Variable.Instance */
+.highlight .vm { color: #19177C } /* Name.Variable.Magic */
+.highlight .il { color: #666 } /* Literal.Number.Integer.Long */
\ No newline at end of file
diff --git a/_static/searchtools.js b/_static/searchtools.js
new file mode 100644
index 0000000..b08d58c
--- /dev/null
+++ b/_static/searchtools.js
@@ -0,0 +1,620 @@
+/*
+ * searchtools.js
+ * ~~~~~~~~~~~~~~~~
+ *
+ * Sphinx JavaScript utilities for the full-text search.
+ *
+ * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+"use strict";
+
+/**
+ * Simple result scoring code.
+ */
+if (typeof Scorer === "undefined") {
+ var Scorer = {
+ // Implement the following function to further tweak the score for each result
+ // The function takes a result array [docname, title, anchor, descr, score, filename]
+ // and returns the new score.
+ /*
+ score: result => {
+ const [docname, title, anchor, descr, score, filename] = result
+ return score
+ },
+ */
+
+ // query matches the full name of an object
+ objNameMatch: 11,
+ // or matches in the last dotted part of the object name
+ objPartialMatch: 6,
+ // Additive scores depending on the priority of the object
+ objPrio: {
+ 0: 15, // used to be importantResults
+ 1: 5, // used to be objectResults
+ 2: -5, // used to be unimportantResults
+ },
+ // Used when the priority is not in the mapping.
+ objPrioDefault: 0,
+
+ // query found in title
+ title: 15,
+ partialTitle: 7,
+ // query found in terms
+ term: 5,
+ partialTerm: 2,
+ };
+}
+
+const _removeChildren = (element) => {
+ while (element && element.lastChild) element.removeChild(element.lastChild);
+};
+
+/**
+ * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
+ */
+const _escapeRegExp = (string) =>
+ string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
+
+const _displayItem = (item, searchTerms, highlightTerms) => {
+ const docBuilder = DOCUMENTATION_OPTIONS.BUILDER;
+ const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX;
+ const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX;
+ const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY;
+ const contentRoot = document.documentElement.dataset.content_root;
+
+ const [docName, title, anchor, descr, score, _filename] = item;
+
+ let listItem = document.createElement("li");
+ let requestUrl;
+ let linkUrl;
+ if (docBuilder === "dirhtml") {
+ // dirhtml builder
+ let dirname = docName + "/";
+ if (dirname.match(/\/index\/$/))
+ dirname = dirname.substring(0, dirname.length - 6);
+ else if (dirname === "index/") dirname = "";
+ requestUrl = contentRoot + dirname;
+ linkUrl = requestUrl;
+ } else {
+ // normal html builders
+ requestUrl = contentRoot + docName + docFileSuffix;
+ linkUrl = docName + docLinkSuffix;
+ }
+ let linkEl = listItem.appendChild(document.createElement("a"));
+ linkEl.href = linkUrl + anchor;
+ linkEl.dataset.score = score;
+ linkEl.innerHTML = title;
+ if (descr) {
+ listItem.appendChild(document.createElement("span")).innerHTML =
+ " (" + descr + ")";
+ // highlight search terms in the description
+ if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
+ highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
+ }
+ else if (showSearchSummary)
+ fetch(requestUrl)
+ .then((responseData) => responseData.text())
+ .then((data) => {
+ if (data)
+ listItem.appendChild(
+ Search.makeSearchSummary(data, searchTerms, anchor)
+ );
+ // highlight search terms in the summary
+ if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
+ highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
+ });
+ Search.output.appendChild(listItem);
+};
+const _finishSearch = (resultCount) => {
+ Search.stopPulse();
+ Search.title.innerText = _("Search Results");
+ if (!resultCount)
+ Search.status.innerText = Documentation.gettext(
+ "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories."
+ );
+ else
+ Search.status.innerText = _(
+ "Search finished, found ${resultCount} page(s) matching the search query."
+ ).replace('${resultCount}', resultCount);
+};
+const _displayNextItem = (
+ results,
+ resultCount,
+ searchTerms,
+ highlightTerms,
+) => {
+ // results left, load the summary and display it
+ // this is intended to be dynamic (don't sub resultsCount)
+ if (results.length) {
+ _displayItem(results.pop(), searchTerms, highlightTerms);
+ setTimeout(
+ () => _displayNextItem(results, resultCount, searchTerms, highlightTerms),
+ 5
+ );
+ }
+ // search finished, update title and status message
+ else _finishSearch(resultCount);
+};
+// Helper function used by query() to order search results.
+// Each input is an array of [docname, title, anchor, descr, score, filename].
+// Order the results by score (in opposite order of appearance, since the
+// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically.
+const _orderResultsByScoreThenName = (a, b) => {
+ const leftScore = a[4];
+ const rightScore = b[4];
+ if (leftScore === rightScore) {
+ // same score: sort alphabetically
+ const leftTitle = a[1].toLowerCase();
+ const rightTitle = b[1].toLowerCase();
+ if (leftTitle === rightTitle) return 0;
+ return leftTitle > rightTitle ? -1 : 1; // inverted is intentional
+ }
+ return leftScore > rightScore ? 1 : -1;
+};
+
+/**
+ * Default splitQuery function. Can be overridden in ``sphinx.search`` with a
+ * custom function per language.
+ *
+ * The regular expression works by splitting the string on consecutive characters
+ * that are not Unicode letters, numbers, underscores, or emoji characters.
+ * This is the same as ``\W+`` in Python, preserving the surrogate pair area.
+ */
+if (typeof splitQuery === "undefined") {
+ var splitQuery = (query) => query
+ .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu)
+ .filter(term => term) // remove remaining empty strings
+}
+
+/**
+ * Search Module
+ */
+const Search = {
+ _index: null,
+ _queued_query: null,
+ _pulse_status: -1,
+
+ htmlToText: (htmlString, anchor) => {
+ const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html');
+ for (const removalQuery of [".headerlink", "script", "style"]) {
+ htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() });
+ }
+ if (anchor) {
+ const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`);
+ if (anchorContent) return anchorContent.textContent;
+
+ console.warn(
+ `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.`
+ );
+ }
+
+ // if anchor not specified or not found, fall back to main content
+ const docContent = htmlElement.querySelector('[role="main"]');
+ if (docContent) return docContent.textContent;
+
+ console.warn(
+ "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template."
+ );
+ return "";
+ },
+
+ init: () => {
+ const query = new URLSearchParams(window.location.search).get("q");
+ document
+ .querySelectorAll('input[name="q"]')
+ .forEach((el) => (el.value = query));
+ if (query) Search.performSearch(query);
+ },
+
+ loadIndex: (url) =>
+ (document.body.appendChild(document.createElement("script")).src = url),
+
+ setIndex: (index) => {
+ Search._index = index;
+ if (Search._queued_query !== null) {
+ const query = Search._queued_query;
+ Search._queued_query = null;
+ Search.query(query);
+ }
+ },
+
+ hasIndex: () => Search._index !== null,
+
+ deferQuery: (query) => (Search._queued_query = query),
+
+ stopPulse: () => (Search._pulse_status = -1),
+
+ startPulse: () => {
+ if (Search._pulse_status >= 0) return;
+
+ const pulse = () => {
+ Search._pulse_status = (Search._pulse_status + 1) % 4;
+ Search.dots.innerText = ".".repeat(Search._pulse_status);
+ if (Search._pulse_status >= 0) window.setTimeout(pulse, 500);
+ };
+ pulse();
+ },
+
+ /**
+ * perform a search for something (or wait until index is loaded)
+ */
+ performSearch: (query) => {
+ // create the required interface elements
+ const searchText = document.createElement("h2");
+ searchText.textContent = _("Searching");
+ const searchSummary = document.createElement("p");
+ searchSummary.classList.add("search-summary");
+ searchSummary.innerText = "";
+ const searchList = document.createElement("ul");
+ searchList.classList.add("search");
+
+ const out = document.getElementById("search-results");
+ Search.title = out.appendChild(searchText);
+ Search.dots = Search.title.appendChild(document.createElement("span"));
+ Search.status = out.appendChild(searchSummary);
+ Search.output = out.appendChild(searchList);
+
+ const searchProgress = document.getElementById("search-progress");
+ // Some themes don't use the search progress node
+ if (searchProgress) {
+ searchProgress.innerText = _("Preparing search...");
+ }
+ Search.startPulse();
+
+ // index already loaded, the browser was quick!
+ if (Search.hasIndex()) Search.query(query);
+ else Search.deferQuery(query);
+ },
+
+ _parseQuery: (query) => {
+ // stem the search terms and add them to the correct list
+ const stemmer = new Stemmer();
+ const searchTerms = new Set();
+ const excludedTerms = new Set();
+ const highlightTerms = new Set();
+ const objectTerms = new Set(splitQuery(query.toLowerCase().trim()));
+ splitQuery(query.trim()).forEach((queryTerm) => {
+ const queryTermLower = queryTerm.toLowerCase();
+
+ // maybe skip this "word"
+ // stopwords array is from language_data.js
+ if (
+ stopwords.indexOf(queryTermLower) !== -1 ||
+ queryTerm.match(/^\d+$/)
+ )
+ return;
+
+ // stem the word
+ let word = stemmer.stemWord(queryTermLower);
+ // select the correct list
+ if (word[0] === "-") excludedTerms.add(word.substr(1));
+ else {
+ searchTerms.add(word);
+ highlightTerms.add(queryTermLower);
+ }
+ });
+
+ if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js
+ localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" "))
+ }
+
+ // console.debug("SEARCH: searching for:");
+ // console.info("required: ", [...searchTerms]);
+ // console.info("excluded: ", [...excludedTerms]);
+
+ return [query, searchTerms, excludedTerms, highlightTerms, objectTerms];
+ },
+
+ /**
+ * execute search (requires search index to be loaded)
+ */
+ _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => {
+ const filenames = Search._index.filenames;
+ const docNames = Search._index.docnames;
+ const titles = Search._index.titles;
+ const allTitles = Search._index.alltitles;
+ const indexEntries = Search._index.indexentries;
+
+ // Collect multiple result groups to be sorted separately and then ordered.
+ // Each is an array of [docname, title, anchor, descr, score, filename].
+ const normalResults = [];
+ const nonMainIndexResults = [];
+
+ _removeChildren(document.getElementById("search-progress"));
+
+ const queryLower = query.toLowerCase().trim();
+ for (const [title, foundTitles] of Object.entries(allTitles)) {
+ if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) {
+ for (const [file, id] of foundTitles) {
+ const score = Math.round(Scorer.title * queryLower.length / title.length);
+ const boost = titles[file] === title ? 1 : 0; // add a boost for document titles
+ normalResults.push([
+ docNames[file],
+ titles[file] !== title ? `${titles[file]} > ${title}` : title,
+ id !== null ? "#" + id : "",
+ null,
+ score + boost,
+ filenames[file],
+ ]);
+ }
+ }
+ }
+
+ // search for explicit entries in index directives
+ for (const [entry, foundEntries] of Object.entries(indexEntries)) {
+ if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) {
+ for (const [file, id, isMain] of foundEntries) {
+ const score = Math.round(100 * queryLower.length / entry.length);
+ const result = [
+ docNames[file],
+ titles[file],
+ id ? "#" + id : "",
+ null,
+ score,
+ filenames[file],
+ ];
+ if (isMain) {
+ normalResults.push(result);
+ } else {
+ nonMainIndexResults.push(result);
+ }
+ }
+ }
+ }
+
+ // lookup as object
+ objectTerms.forEach((term) =>
+ normalResults.push(...Search.performObjectSearch(term, objectTerms))
+ );
+
+ // lookup as search terms in fulltext
+ normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms));
+
+ // let the scorer override scores with a custom scoring function
+ if (Scorer.score) {
+ normalResults.forEach((item) => (item[4] = Scorer.score(item)));
+ nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item)));
+ }
+
+ // Sort each group of results by score and then alphabetically by name.
+ normalResults.sort(_orderResultsByScoreThenName);
+ nonMainIndexResults.sort(_orderResultsByScoreThenName);
+
+ // Combine the result groups in (reverse) order.
+ // Non-main index entries are typically arbitrary cross-references,
+ // so display them after other results.
+ let results = [...nonMainIndexResults, ...normalResults];
+
+ // remove duplicate search results
+ // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept
+ let seen = new Set();
+ results = results.reverse().reduce((acc, result) => {
+ let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(',');
+ if (!seen.has(resultStr)) {
+ acc.push(result);
+ seen.add(resultStr);
+ }
+ return acc;
+ }, []);
+
+ return results.reverse();
+ },
+
+ query: (query) => {
+ const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query);
+ const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms);
+
+ // for debugging
+ //Search.lastresults = results.slice(); // a copy
+ // console.info("search results:", Search.lastresults);
+
+ // print the results
+ _displayNextItem(results, results.length, searchTerms, highlightTerms);
+ },
+
+ /**
+ * search for object names
+ */
+ performObjectSearch: (object, objectTerms) => {
+ const filenames = Search._index.filenames;
+ const docNames = Search._index.docnames;
+ const objects = Search._index.objects;
+ const objNames = Search._index.objnames;
+ const titles = Search._index.titles;
+
+ const results = [];
+
+ const objectSearchCallback = (prefix, match) => {
+ const name = match[4]
+ const fullname = (prefix ? prefix + "." : "") + name;
+ const fullnameLower = fullname.toLowerCase();
+ if (fullnameLower.indexOf(object) < 0) return;
+
+ let score = 0;
+ const parts = fullnameLower.split(".");
+
+ // check for different match types: exact matches of full name or
+ // "last name" (i.e. last dotted part)
+ if (fullnameLower === object || parts.slice(-1)[0] === object)
+ score += Scorer.objNameMatch;
+ else if (parts.slice(-1)[0].indexOf(object) > -1)
+ score += Scorer.objPartialMatch; // matches in last name
+
+ const objName = objNames[match[1]][2];
+ const title = titles[match[0]];
+
+ // If more than one term searched for, we require other words to be
+ // found in the name/title/description
+ const otherTerms = new Set(objectTerms);
+ otherTerms.delete(object);
+ if (otherTerms.size > 0) {
+ const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase();
+ if (
+ [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0)
+ )
+ return;
+ }
+
+ let anchor = match[3];
+ if (anchor === "") anchor = fullname;
+ else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname;
+
+ const descr = objName + _(", in ") + title;
+
+ // add custom score for some objects according to scorer
+ if (Scorer.objPrio.hasOwnProperty(match[2]))
+ score += Scorer.objPrio[match[2]];
+ else score += Scorer.objPrioDefault;
+
+ results.push([
+ docNames[match[0]],
+ fullname,
+ "#" + anchor,
+ descr,
+ score,
+ filenames[match[0]],
+ ]);
+ };
+ Object.keys(objects).forEach((prefix) =>
+ objects[prefix].forEach((array) =>
+ objectSearchCallback(prefix, array)
+ )
+ );
+ return results;
+ },
+
+ /**
+ * search for full-text terms in the index
+ */
+ performTermsSearch: (searchTerms, excludedTerms) => {
+ // prepare search
+ const terms = Search._index.terms;
+ const titleTerms = Search._index.titleterms;
+ const filenames = Search._index.filenames;
+ const docNames = Search._index.docnames;
+ const titles = Search._index.titles;
+
+ const scoreMap = new Map();
+ const fileMap = new Map();
+
+ // perform the search on the required terms
+ searchTerms.forEach((word) => {
+ const files = [];
+ const arr = [
+ { files: terms[word], score: Scorer.term },
+ { files: titleTerms[word], score: Scorer.title },
+ ];
+ // add support for partial matches
+ if (word.length > 2) {
+ const escapedWord = _escapeRegExp(word);
+ if (!terms.hasOwnProperty(word)) {
+ Object.keys(terms).forEach((term) => {
+ if (term.match(escapedWord))
+ arr.push({ files: terms[term], score: Scorer.partialTerm });
+ });
+ }
+ if (!titleTerms.hasOwnProperty(word)) {
+ Object.keys(titleTerms).forEach((term) => {
+ if (term.match(escapedWord))
+ arr.push({ files: titleTerms[term], score: Scorer.partialTitle });
+ });
+ }
+ }
+
+ // no match but word was a required one
+ if (arr.every((record) => record.files === undefined)) return;
+
+ // found search word in contents
+ arr.forEach((record) => {
+ if (record.files === undefined) return;
+
+ let recordFiles = record.files;
+ if (recordFiles.length === undefined) recordFiles = [recordFiles];
+ files.push(...recordFiles);
+
+ // set score for the word in each file
+ recordFiles.forEach((file) => {
+ if (!scoreMap.has(file)) scoreMap.set(file, {});
+ scoreMap.get(file)[word] = record.score;
+ });
+ });
+
+ // create the mapping
+ files.forEach((file) => {
+ if (!fileMap.has(file)) fileMap.set(file, [word]);
+ else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word);
+ });
+ });
+
+ // now check if the files don't contain excluded terms
+ const results = [];
+ for (const [file, wordList] of fileMap) {
+ // check if all requirements are matched
+
+ // as search terms with length < 3 are discarded
+ const filteredTermCount = [...searchTerms].filter(
+ (term) => term.length > 2
+ ).length;
+ if (
+ wordList.length !== searchTerms.size &&
+ wordList.length !== filteredTermCount
+ )
+ continue;
+
+ // ensure that none of the excluded terms is in the search result
+ if (
+ [...excludedTerms].some(
+ (term) =>
+ terms[term] === file ||
+ titleTerms[term] === file ||
+ (terms[term] || []).includes(file) ||
+ (titleTerms[term] || []).includes(file)
+ )
+ )
+ break;
+
+ // select one (max) score for the file.
+ const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w]));
+ // add result to the result list
+ results.push([
+ docNames[file],
+ titles[file],
+ "",
+ null,
+ score,
+ filenames[file],
+ ]);
+ }
+ return results;
+ },
+
+ /**
+ * helper function to return a node containing the
+ * search summary for a given text. keywords is a list
+ * of stemmed words.
+ */
+ makeSearchSummary: (htmlText, keywords, anchor) => {
+ const text = Search.htmlToText(htmlText, anchor);
+ if (text === "") return null;
+
+ const textLower = text.toLowerCase();
+ const actualStartPosition = [...keywords]
+ .map((k) => textLower.indexOf(k.toLowerCase()))
+ .filter((i) => i > -1)
+ .slice(-1)[0];
+ const startWithContext = Math.max(actualStartPosition - 120, 0);
+
+ const top = startWithContext === 0 ? "" : "...";
+ const tail = startWithContext + 240 < text.length ? "..." : "";
+
+ let summary = document.createElement("p");
+ summary.classList.add("context");
+ summary.textContent = top + text.substr(startWithContext, 240).trim() + tail;
+
+ return summary;
+ },
+};
+
+_ready(Search.init);
diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js
new file mode 100644
index 0000000..8a96c69
--- /dev/null
+++ b/_static/sphinx_highlight.js
@@ -0,0 +1,154 @@
+/* Highlighting utilities for Sphinx HTML documentation. */
+"use strict";
+
+const SPHINX_HIGHLIGHT_ENABLED = true
+
+/**
+ * highlight a given string on a node by wrapping it in
+ * span elements with the given class name.
+ */
+const _highlight = (node, addItems, text, className) => {
+ if (node.nodeType === Node.TEXT_NODE) {
+ const val = node.nodeValue;
+ const parent = node.parentNode;
+ const pos = val.toLowerCase().indexOf(text);
+ if (
+ pos >= 0 &&
+ !parent.classList.contains(className) &&
+ !parent.classList.contains("nohighlight")
+ ) {
+ let span;
+
+ const closestNode = parent.closest("body, svg, foreignObject");
+ const isInSVG = closestNode && closestNode.matches("svg");
+ if (isInSVG) {
+ span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
+ } else {
+ span = document.createElement("span");
+ span.classList.add(className);
+ }
+
+ span.appendChild(document.createTextNode(val.substr(pos, text.length)));
+ const rest = document.createTextNode(val.substr(pos + text.length));
+ parent.insertBefore(
+ span,
+ parent.insertBefore(
+ rest,
+ node.nextSibling
+ )
+ );
+ node.nodeValue = val.substr(0, pos);
+ /* There may be more occurrences of search term in this node. So call this
+ * function recursively on the remaining fragment.
+ */
+ _highlight(rest, addItems, text, className);
+
+ if (isInSVG) {
+ const rect = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "rect"
+ );
+ const bbox = parent.getBBox();
+ rect.x.baseVal.value = bbox.x;
+ rect.y.baseVal.value = bbox.y;
+ rect.width.baseVal.value = bbox.width;
+ rect.height.baseVal.value = bbox.height;
+ rect.setAttribute("class", className);
+ addItems.push({ parent: parent, target: rect });
+ }
+ }
+ } else if (node.matches && !node.matches("button, select, textarea")) {
+ node.childNodes.forEach((el) => _highlight(el, addItems, text, className));
+ }
+};
+const _highlightText = (thisNode, text, className) => {
+ let addItems = [];
+ _highlight(thisNode, addItems, text, className);
+ addItems.forEach((obj) =>
+ obj.parent.insertAdjacentElement("beforebegin", obj.target)
+ );
+};
+
+/**
+ * Small JavaScript module for the documentation.
+ */
+const SphinxHighlight = {
+
+ /**
+ * highlight the search words provided in localstorage in the text
+ */
+ highlightSearchWords: () => {
+ if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight
+
+ // get and clear terms from localstorage
+ const url = new URL(window.location);
+ const highlight =
+ localStorage.getItem("sphinx_highlight_terms")
+ || url.searchParams.get("highlight")
+ || "";
+ localStorage.removeItem("sphinx_highlight_terms")
+ url.searchParams.delete("highlight");
+ window.history.replaceState({}, "", url);
+
+ // get individual terms from highlight string
+ const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
+ if (terms.length === 0) return; // nothing to do
+
+ // There should never be more than one element matching "div.body"
+ const divBody = document.querySelectorAll("div.body");
+ const body = divBody.length ? divBody[0] : document.querySelector("body");
+ window.setTimeout(() => {
+ terms.forEach((term) => _highlightText(body, term, "highlighted"));
+ }, 10);
+
+ const searchBox = document.getElementById("searchbox");
+ if (searchBox === null) return;
+ searchBox.appendChild(
+ document
+ .createRange()
+ .createContextualFragment(
+ '' +
+ '' +
+ _("Hide Search Matches") +
+ "
"
+ )
+ );
+ },
+
+ /**
+ * helper function to hide the search marks again
+ */
+ hideSearchWords: () => {
+ document
+ .querySelectorAll("#searchbox .highlight-link")
+ .forEach((el) => el.remove());
+ document
+ .querySelectorAll("span.highlighted")
+ .forEach((el) => el.classList.remove("highlighted"));
+ localStorage.removeItem("sphinx_highlight_terms")
+ },
+
+ initEscapeListener: () => {
+ // only install a listener if it is really needed
+ if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return;
+
+ document.addEventListener("keydown", (event) => {
+ // bail for input elements
+ if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
+ // bail with special keys
+ if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;
+ if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) {
+ SphinxHighlight.hideSearchWords();
+ event.preventDefault();
+ }
+ });
+ },
+};
+
+_ready(() => {
+ /* Do not call highlightSearchWords() when we are on the search page.
+ * It will highlight words from the *previous* search query.
+ */
+ if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords();
+ SphinxHighlight.initEscapeListener();
+});
diff --git a/_static/sphinx_lesson.css b/_static/sphinx_lesson.css
new file mode 100644
index 0000000..68cb32d
--- /dev/null
+++ b/_static/sphinx_lesson.css
@@ -0,0 +1,103 @@
+/* sphinx_lesson.css
+ * https://webaim.org/resources/contrastchecker/?fcolor=00000&bcolor=FCE762
+ * */
+:root {
+ --sphinx-lesson-selection-bg-color: #fce762;
+ --sphinx-lesson-selection-fg-color: #000000;
+}
+
+/* https://webaim.org/resources/contrastchecker/?fcolor=FFFFFF&bcolor=745315
+ * when dark theme is selected the some themes use this attirbute
+ */
+html[data-theme='dark'], body[data-theme='dark'] {
+ --sphinx-lesson-selection-bg-color: #745315;
+ --sphinx-lesson-selection-fg-color: #ffffff;
+}
+
+/* when browser/system theme is dark and no theme is selected */
+@media (prefers-color-scheme: dark) {
+ html[data-theme='auto'], body[data-theme='auto'] {
+ --sphinx-lesson-selection-bg-color: #745315;
+ --sphinx-lesson-selection-fg-color: #ffffff;
+ }
+}
+
+body.wy-body-for-nav img.with-border {
+ border: 2px solid;
+}
+
+.rst-content .admonition-no-content {
+ padding-bottom: 0px;
+}
+
+.rst-content .demo > .admonition-title::before {
+ content: "\01F440"; /* Eyes */ }
+.rst-content .type-along > .admonition-title::before {
+ content: "\02328\0FE0F"; /* Keyboard */ }
+.rst-content .exercise > .admonition-title::before {
+ content: "\0270D\0FE0F"; /* Hand */ }
+.rst-content .solution > .admonition-title::before {
+ content: "\02714\0FE0E"; /* Check mark */ }
+.rst-content .homework > .admonition-title::before {
+ content: "\01F4DD"; /* Memo */ }
+.rst-content .discussion > .admonition-title::before {
+ content: "\01F4AC"; /* Speech balloon */ }
+.rst-content .questions > .admonition-title::before {
+ content: "\02753\0FE0E"; /* Question mark */ }
+.rst-content .prerequisites > .admonition-title::before {
+ content: "\02699"; /* Gear */ }
+.rst-content .seealso > .admonition-title::before {
+ content: "\027A1\0FE0E"; /* Question mark */ }
+
+
+/* instructor-note */
+.rst-content .instructor-note {
+ background: #e7e7e7;
+}
+.rst-content .instructor-note > .admonition-title {
+ background: #6a6a6a;
+}
+.rst-content .instructor-note > .admonition-title::before {
+ content: "";
+}
+
+
+/* sphinx_toggle_button, make the font white */
+.rst-content .toggle.admonition button.toggle-button {
+ color: white;
+}
+
+/* sphinx-togglebutton, remove underflow when toggled to hidden mode */
+.rst-content .admonition.toggle-hidden {
+ padding-bottom: 0px;
+}
+
+/* selection / highlight colour uses a yellow background and a black text */
+/*** Works on common browsers ***/
+::selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
+
+/*** Mozilla based browsers ***/
+::-moz-selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
+
+/***For Other Browsers ***/
+::-o-selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
+
+::-ms-selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
+
+/*** For Webkit ***/
+::-webkit-selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
diff --git a/_static/sphinx_rtd_theme_ext_color_contrast.css b/_static/sphinx_rtd_theme_ext_color_contrast.css
new file mode 100644
index 0000000..e68feb8
--- /dev/null
+++ b/_static/sphinx_rtd_theme_ext_color_contrast.css
@@ -0,0 +1,47 @@
+/* The following are for web accessibility of sphinx_rtd_theme: they
+ * solve some of the most frequent contrast issues. Remove when this
+ * solved:
+ * https://github.com/readthedocs/sphinx_rtd_theme/issues/971
+ */
+/* background: #fcfcfc, note boxes #E7F2FA */
+a { color: #2573A7; } /* original #2980B9, #1F5C84; */
+body { color: #242424; } /* original #404040, #383838 */
+.wy-side-nav-search>a, .wy-side-nav-search .wy-dropdown>a {
+ color: #ffffff;
+} /* original #fcfcfc */
+footer { color: #737373; } /* original gray=#808080*/
+footer span.commit code, footer span.commit .rst-content tt, .rst-content footer span.commit tt {
+ color: #737373;
+} /* original gray=#808080*/
+.rst-content tt.literal, .rst-content tt.literal, .rst-content code.literal {
+ color: #AB2314;
+}
+/* Sidebar background */
+.wy-side-nav-search { background-color: #277CB4;}
+
+/* Same, but for pygments */
+.highlight .ch { color: #3E7A89; } /* #! line */
+.highlight .c1 { color: #3E7A89; } /* also comments */
+.highlight .nv { color: #AD3ECC; } /* variable */
+.highlight .gp { color: #B45608; } /* prompt character, $*/
+.highlight .si { color: #3975B1; } /* ${} variable text */
+.highlight .nc { color: #0C78A7; }
+
+/* Sphinx admonitions */
+/* warning */
+.wy-alert.wy-alert-warning .wy-alert-title, .rst-content .wy-alert-warning.note .wy-alert-title, .rst-content .attention .wy-alert-title, .rst-content .caution .wy-alert-title, .rst-content .wy-alert-warning.danger .wy-alert-title, .rst-content .wy-alert-warning.error .wy-alert-title, .rst-content .wy-alert-warning.hint .wy-alert-title, .rst-content .wy-alert-warning.important .wy-alert-title, .rst-content .wy-alert-warning.tip .wy-alert-title, .rst-content .warning .wy-alert-title, .rst-content .wy-alert-warning.seealso .wy-alert-title, .rst-content .admonition-todo .wy-alert-title, .rst-content .wy-alert-warning.admonition .wy-alert-title, .wy-alert.wy-alert-warning .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-warning .admonition-title, .rst-content .wy-alert-warning.note .admonition-title, .rst-content .attention .admonition-title, .rst-content .caution .admonition-title, .rst-content .wy-alert-warning.danger .admonition-title, .rst-content .wy-alert-warning.error .admonition-title, .rst-content .wy-alert-warning.hint .admonition-title, .rst-content .wy-alert-warning.important .admonition-title, .rst-content .wy-alert-warning.tip .admonition-title, .rst-content .warning .admonition-title, .rst-content .wy-alert-warning.seealso .admonition-title, .rst-content .admonition-todo .admonition-title, .rst-content .wy-alert-warning.admonition .admonition-title {
+ background: #B15E16; }
+/* important */
+.wy-alert.wy-alert-success .wy-alert-title, .rst-content .wy-alert-success.note .wy-alert-title, .rst-content .wy-alert-success.attention .wy-alert-title, .rst-content .wy-alert-success.caution .wy-alert-title, .rst-content .wy-alert-success.danger .wy-alert-title, .rst-content .wy-alert-success.error .wy-alert-title, .rst-content .hint .wy-alert-title, .rst-content .important .wy-alert-title, .rst-content .tip .wy-alert-title, .rst-content .wy-alert-success.warning .wy-alert-title, .rst-content .wy-alert-success.seealso .wy-alert-title, .rst-content .wy-alert-success.admonition-todo .wy-alert-title, .rst-content .wy-alert-success.admonition .wy-alert-title, .wy-alert.wy-alert-success .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-success .admonition-title, .rst-content .wy-alert-success.note .admonition-title, .rst-content .wy-alert-success.attention .admonition-title, .rst-content .wy-alert-success.caution .admonition-title, .rst-content .wy-alert-success.danger .admonition-title, .rst-content .wy-alert-success.error .admonition-title, .rst-content .hint .admonition-title, .rst-content .important .admonition-title, .rst-content .tip .admonition-title, .rst-content .wy-alert-success.warning .admonition-title, .rst-content .wy-alert-success.seealso .admonition-title, .rst-content .wy-alert-success.admonition-todo .admonition-title, .rst-content .wy-alert-success.admonition .admonition-title {
+ background: #12826C; }
+/* seealso, note, etc */
+.wy-alert.wy-alert-info .wy-alert-title, .rst-content .note .wy-alert-title, .rst-content .wy-alert-info.attention .wy-alert-title, .rst-content .wy-alert-info.caution .wy-alert-title, .rst-content .wy-alert-info.danger .wy-alert-title, .rst-content .wy-alert-info.error .wy-alert-title, .rst-content .wy-alert-info.hint .wy-alert-title, .rst-content .wy-alert-info.important .wy-alert-title, .rst-content .wy-alert-info.tip .wy-alert-title, .rst-content .wy-alert-info.warning .wy-alert-title, .rst-content .seealso .wy-alert-title, .rst-content .wy-alert-info.admonition-todo .wy-alert-title, .rst-content .wy-alert-info.admonition .wy-alert-title, .wy-alert.wy-alert-info .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-info .admonition-title, .rst-content .note .admonition-title, .rst-content .wy-alert-info.attention .admonition-title, .rst-content .wy-alert-info.caution .admonition-title, .rst-content .wy-alert-info.danger .admonition-title, .rst-content .wy-alert-info.error .admonition-title, .rst-content .wy-alert-info.hint .admonition-title, .rst-content .wy-alert-info.important .admonition-title, .rst-content .wy-alert-info.tip .admonition-title, .rst-content .wy-alert-info.warning .admonition-title, .rst-content .seealso .admonition-title, .rst-content .wy-alert-info.admonition-todo .admonition-title, .rst-content .wy-alert-info.admonition .admonition-title {
+ background: #277CB4; }
+/* error, danger */
+.rst-content .danger .admonition-title, .rst-content .danger .wy-alert-title, .rst-content .error .admonition-title, .rst-content .error .wy-alert-title, .rst-content .wy-alert-danger.admonition-todo .admonition-title, .rst-content .wy-alert-danger.admonition-todo .wy-alert-title, .rst-content .wy-alert-danger.admonition .admonition-title, .rst-content .wy-alert-danger.admonition .wy-alert-title, .rst-content .wy-alert-danger.attention .admonition-title, .rst-content .wy-alert-danger.attention .wy-alert-title, .rst-content .wy-alert-danger.caution .admonition-title, .rst-content .wy-alert-danger.caution .wy-alert-title, .rst-content .wy-alert-danger.hint .admonition-title, .rst-content .wy-alert-danger.hint .wy-alert-title, .rst-content .wy-alert-danger.important .admonition-title, .rst-content .wy-alert-danger.important .wy-alert-title, .rst-content .wy-alert-danger.note .admonition-title, .rst-content .wy-alert-danger.note .wy-alert-title, .rst-content .wy-alert-danger.seealso .admonition-title, .rst-content .wy-alert-danger.seealso .wy-alert-title, .rst-content .wy-alert-danger.tip .admonition-title, .rst-content .wy-alert-danger.tip .wy-alert-title, .rst-content .wy-alert-danger.warning .admonition-title, .rst-content .wy-alert-danger.warning .wy-alert-title, .rst-content .wy-alert.wy-alert-danger .admonition-title, .wy-alert.wy-alert-danger .rst-content .admonition-title, .wy-alert.wy-alert-danger .wy-alert-title {
+ background: #e31704;
+}
+
+/* Generic admonition titles */
+.wy-alert-title, .rst-content .admonition-title {
+ background: #277CB4; }
diff --git a/_static/style.css b/_static/style.css
new file mode 100644
index 0000000..59e60ee
--- /dev/null
+++ b/_static/style.css
@@ -0,0 +1,6 @@
+.rst-content .objectives {
+ background: #fee0d2;
+}
+.rst-content .objectives > .admonition-title {
+ background: #fc9272;
+}
diff --git a/_static/tabs.css b/_static/tabs.css
new file mode 100644
index 0000000..957ba60
--- /dev/null
+++ b/_static/tabs.css
@@ -0,0 +1,89 @@
+.sphinx-tabs {
+ margin-bottom: 1rem;
+}
+
+[role="tablist"] {
+ border-bottom: 1px solid #a0b3bf;
+}
+
+.sphinx-tabs-tab {
+ position: relative;
+ font-family: Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;
+ color: #1D5C87;
+ line-height: 24px;
+ margin: 0;
+ font-size: 16px;
+ font-weight: 400;
+ background-color: rgba(255, 255, 255, 0);
+ border-radius: 5px 5px 0 0;
+ border: 0;
+ padding: 1rem 1.5rem;
+ margin-bottom: 0;
+}
+
+.sphinx-tabs-tab[aria-selected="true"] {
+ font-weight: 700;
+ border: 1px solid #a0b3bf;
+ border-bottom: 1px solid white;
+ margin: -1px;
+ background-color: white;
+}
+
+.sphinx-tabs-tab:focus {
+ z-index: 1;
+ outline-offset: 1px;
+}
+
+.sphinx-tabs-panel {
+ position: relative;
+ padding: 1rem;
+ border: 1px solid #a0b3bf;
+ margin: 0px -1px -1px -1px;
+ border-radius: 0 0 5px 5px;
+ border-top: 0;
+ background: white;
+}
+
+.sphinx-tabs-panel.code-tab {
+ padding: 0.4rem;
+}
+
+.sphinx-tab img {
+ margin-bottom: 24 px;
+}
+
+/* Dark theme preference styling */
+
+@media (prefers-color-scheme: dark) {
+ body[data-theme="auto"] .sphinx-tabs-panel {
+ color: white;
+ background-color: rgb(50, 50, 50);
+ }
+
+ body[data-theme="auto"] .sphinx-tabs-tab {
+ color: white;
+ background-color: rgba(255, 255, 255, 0.05);
+ }
+
+ body[data-theme="auto"] .sphinx-tabs-tab[aria-selected="true"] {
+ border-bottom: 1px solid rgb(50, 50, 50);
+ background-color: rgb(50, 50, 50);
+ }
+}
+
+/* Explicit dark theme styling */
+
+body[data-theme="dark"] .sphinx-tabs-panel {
+ color: white;
+ background-color: rgb(50, 50, 50);
+}
+
+body[data-theme="dark"] .sphinx-tabs-tab {
+ color: white;
+ background-color: rgba(255, 255, 255, 0.05);
+}
+
+body[data-theme="dark"] .sphinx-tabs-tab[aria-selected="true"] {
+ border-bottom: 2px solid rgb(50, 50, 50);
+ background-color: rgb(50, 50, 50);
+}
diff --git a/_static/tabs.js b/_static/tabs.js
new file mode 100644
index 0000000..48dc303
--- /dev/null
+++ b/_static/tabs.js
@@ -0,0 +1,145 @@
+try {
+ var session = window.sessionStorage || {};
+} catch (e) {
+ var session = {};
+}
+
+window.addEventListener("DOMContentLoaded", () => {
+ const allTabs = document.querySelectorAll('.sphinx-tabs-tab');
+ const tabLists = document.querySelectorAll('[role="tablist"]');
+
+ allTabs.forEach(tab => {
+ tab.addEventListener("click", changeTabs);
+ });
+
+ tabLists.forEach(tabList => {
+ tabList.addEventListener("keydown", keyTabs);
+ });
+
+ // Restore group tab selection from session
+ const lastSelected = session.getItem('sphinx-tabs-last-selected');
+ if (lastSelected != null) selectNamedTabs(lastSelected);
+});
+
+/**
+ * Key focus left and right between sibling elements using arrows
+ * @param {Node} e the element in focus when key was pressed
+ */
+function keyTabs(e) {
+ const tab = e.target;
+ let nextTab = null;
+ if (e.keyCode === 39 || e.keyCode === 37) {
+ tab.setAttribute("tabindex", -1);
+ // Move right
+ if (e.keyCode === 39) {
+ nextTab = tab.nextElementSibling;
+ if (nextTab === null) {
+ nextTab = tab.parentNode.firstElementChild;
+ }
+ // Move left
+ } else if (e.keyCode === 37) {
+ nextTab = tab.previousElementSibling;
+ if (nextTab === null) {
+ nextTab = tab.parentNode.lastElementChild;
+ }
+ }
+ }
+
+ if (nextTab !== null) {
+ nextTab.setAttribute("tabindex", 0);
+ nextTab.focus();
+ }
+}
+
+/**
+ * Select or deselect clicked tab. If a group tab
+ * is selected, also select tab in other tabLists.
+ * @param {Node} e the element that was clicked
+ */
+function changeTabs(e) {
+ // Use this instead of the element that was clicked, in case it's a child
+ const notSelected = this.getAttribute("aria-selected") === "false";
+ const positionBefore = this.parentNode.getBoundingClientRect().top;
+ const notClosable = !this.parentNode.classList.contains("closeable");
+
+ deselectTabList(this);
+
+ if (notSelected || notClosable) {
+ selectTab(this);
+ const name = this.getAttribute("name");
+ selectNamedTabs(name, this.id);
+
+ if (this.classList.contains("group-tab")) {
+ // Persist during session
+ session.setItem('sphinx-tabs-last-selected', name);
+ }
+ }
+
+ const positionAfter = this.parentNode.getBoundingClientRect().top;
+ const positionDelta = positionAfter - positionBefore;
+ // Scroll to offset content resizing
+ window.scrollTo(0, window.scrollY + positionDelta);
+}
+
+/**
+ * Select tab and show associated panel.
+ * @param {Node} tab tab to select
+ */
+function selectTab(tab) {
+ tab.setAttribute("aria-selected", true);
+
+ // Show the associated panel
+ document
+ .getElementById(tab.getAttribute("aria-controls"))
+ .removeAttribute("hidden");
+}
+
+/**
+ * Hide the panels associated with all tabs within the
+ * tablist containing this tab.
+ * @param {Node} tab a tab within the tablist to deselect
+ */
+function deselectTabList(tab) {
+ const parent = tab.parentNode;
+ const grandparent = parent.parentNode;
+
+ Array.from(parent.children)
+ .forEach(t => t.setAttribute("aria-selected", false));
+
+ Array.from(grandparent.children)
+ .slice(1) // Skip tablist
+ .forEach(panel => panel.setAttribute("hidden", true));
+}
+
+/**
+ * Select grouped tabs with the same name, but no the tab
+ * with the given id.
+ * @param {Node} name name of grouped tab to be selected
+ * @param {Node} clickedId id of clicked tab
+ */
+function selectNamedTabs(name, clickedId=null) {
+ const groupedTabs = document.querySelectorAll(`.sphinx-tabs-tab[name="${name}"]`);
+ const tabLists = Array.from(groupedTabs).map(tab => tab.parentNode);
+
+ tabLists
+ .forEach(tabList => {
+ // Don't want to change the tabList containing the clicked tab
+ const clickedTab = tabList.querySelector(`[id="${clickedId}"]`);
+ if (clickedTab === null ) {
+ // Select first tab with matching name
+ const tab = tabList.querySelector(`.sphinx-tabs-tab[name="${name}"]`);
+ deselectTabList(tab);
+ selectTab(tab);
+ }
+ })
+}
+
+if (typeof exports === 'undefined') {
+ exports = {};
+}
+
+exports.keyTabs = keyTabs;
+exports.changeTabs = changeTabs;
+exports.selectTab = selectTab;
+exports.deselectTabList = deselectTabList;
+exports.selectNamedTabs = selectNamedTabs;
diff --git a/_static/term_role_formatting.css b/_static/term_role_formatting.css
new file mode 100644
index 0000000..0b66095
--- /dev/null
+++ b/_static/term_role_formatting.css
@@ -0,0 +1,4 @@
+/* Make terms bold */
+a.reference span.std-term {
+ font-weight: bold;
+}
diff --git a/_static/togglebutton.css b/_static/togglebutton.css
new file mode 100644
index 0000000..54a6787
--- /dev/null
+++ b/_static/togglebutton.css
@@ -0,0 +1,160 @@
+/**
+ * Admonition-based toggles
+ */
+
+/* Visibility of the target */
+.admonition.toggle .admonition-title ~ * {
+ transition: opacity .3s, height .3s;
+}
+
+/* Toggle buttons inside admonitions so we see the title */
+.admonition.toggle {
+ position: relative;
+}
+
+/* Titles should cut off earlier to avoid overlapping w/ button */
+.admonition.toggle .admonition-title {
+ padding-right: 25%;
+ cursor: pointer;
+}
+
+/* Hovering will cause a slight shift in color to make it feel interactive */
+.admonition.toggle .admonition-title:hover {
+ box-shadow: inset 0 0 0px 20px rgb(0 0 0 / 1%);
+}
+
+/* Hovering will cause a slight shift in color to make it feel interactive */
+.admonition.toggle .admonition-title:active {
+ box-shadow: inset 0 0 0px 20px rgb(0 0 0 / 3%);
+}
+
+/* Remove extra whitespace below the admonition title when hidden */
+.admonition.toggle-hidden {
+ padding-bottom: 0;
+}
+
+.admonition.toggle-hidden .admonition-title {
+ margin-bottom: 0;
+}
+
+/* hides all the content of a page until de-toggled */
+.admonition.toggle-hidden .admonition-title ~ * {
+ height: 0;
+ margin: 0;
+ opacity: 0;
+ visibility: hidden;
+}
+
+/* General button style and position*/
+button.toggle-button {
+ /**
+ * Background and shape. By default there's no background
+ * but users can style as they wish
+ */
+ background: none;
+ border: none;
+ outline: none;
+
+ /* Positioning just inside the admonition title */
+ position: absolute;
+ right: 0.5em;
+ padding: 0px;
+ border: none;
+ outline: none;
+}
+
+/* Display the toggle hint on wide screens */
+@media (min-width: 768px) {
+ button.toggle-button.toggle-button-hidden:before {
+ content: attr(data-toggle-hint); /* This will be filled in by JS */
+ font-size: .8em;
+ align-self: center;
+ }
+}
+
+/* Icon behavior */
+.tb-icon {
+ transition: transform .2s ease-out;
+ height: 1.5em;
+ width: 1.5em;
+ stroke: currentColor; /* So that we inherit the color of other text */
+}
+
+/* The icon should point right when closed, down when open. */
+/* Open */
+.admonition.toggle button .tb-icon {
+ transform: rotate(90deg);
+}
+
+/* Closed */
+.admonition.toggle button.toggle-button-hidden .tb-icon {
+ transform: rotate(0deg);
+}
+
+/* With details toggles, we don't rotate the icon so it points right */
+details.toggle-details .tb-icon {
+ height: 1.4em;
+ width: 1.4em;
+ margin-top: 0.1em; /* To center the button vertically */
+}
+
+
+/**
+ * Details-based toggles.
+ * In this case, we wrap elements with `.toggle` in a details block.
+ */
+
+/* Details blocks */
+details.toggle-details {
+ margin: 1em 0;
+}
+
+
+details.toggle-details summary {
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ list-style: none;
+ border-radius: .2em;
+ border-left: 3px solid #1976d2;
+ background-color: rgb(204 204 204 / 10%);
+ padding: 0.2em 0.7em 0.3em 0.5em; /* Less padding on left because the SVG has left margin */
+ font-size: 0.9em;
+}
+
+details.toggle-details summary:hover {
+ background-color: rgb(204 204 204 / 20%);
+}
+
+details.toggle-details summary:active {
+ background: rgb(204 204 204 / 28%);
+}
+
+.toggle-details__summary-text {
+ margin-left: 0.2em;
+}
+
+details.toggle-details[open] summary {
+ margin-bottom: .5em;
+}
+
+details.toggle-details[open] summary .tb-icon {
+ transform: rotate(90deg);
+}
+
+details.toggle-details[open] summary ~ * {
+ animation: toggle-fade-in .3s ease-out;
+}
+
+@keyframes toggle-fade-in {
+ from {opacity: 0%;}
+ to {opacity: 100%;}
+}
+
+/* Print rules - we hide all toggle button elements at print */
+@media print {
+ /* Always hide the summary so the button doesn't show up */
+ details.toggle-details summary {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/_static/togglebutton.js b/_static/togglebutton.js
new file mode 100644
index 0000000..215a7ee
--- /dev/null
+++ b/_static/togglebutton.js
@@ -0,0 +1,187 @@
+/**
+ * Add Toggle Buttons to elements
+ */
+
+let toggleChevron = `
+
+
+
+ `;
+
+var initToggleItems = () => {
+ var itemsToToggle = document.querySelectorAll(togglebuttonSelector);
+ console.log(`[togglebutton]: Adding toggle buttons to ${itemsToToggle.length} items`)
+ // Add the button to each admonition and hook up a callback to toggle visibility
+ itemsToToggle.forEach((item, index) => {
+ if (item.classList.contains("admonition")) {
+ // If it's an admonition block, then we'll add a button inside
+ // Generate unique IDs for this item
+ var toggleID = `toggle-${index}`;
+ var buttonID = `button-${toggleID}`;
+
+ item.setAttribute('id', toggleID);
+ if (!item.classList.contains("toggle")){
+ item.classList.add("toggle");
+ }
+ // This is the button that will be added to each item to trigger the toggle
+ var collapseButton = `
+
+ ${toggleChevron}
+ `;
+
+ title = item.querySelector(".admonition-title")
+ title.insertAdjacentHTML("beforeend", collapseButton);
+ thisButton = document.getElementById(buttonID);
+
+ // Add click handlers for the button + admonition title (if admonition)
+ admonitionTitle = document.querySelector(`#${toggleID} > .admonition-title`)
+ if (admonitionTitle) {
+ // If an admonition, then make the whole title block clickable
+ admonitionTitle.addEventListener('click', toggleClickHandler);
+ admonitionTitle.dataset.target = toggleID
+ admonitionTitle.dataset.button = buttonID
+ } else {
+ // If not an admonition then we'll listen for the button click
+ thisButton.addEventListener('click', toggleClickHandler);
+ }
+
+ // Now hide the item for this toggle button unless explicitly noted to show
+ if (!item.classList.contains("toggle-shown")) {
+ toggleHidden(thisButton);
+ }
+ } else {
+ // If not an admonition, wrap the block in a block
+ // Define the structure of the details block and insert it as a sibling
+ var detailsBlock = `
+
+
+ ${toggleChevron}
+ ${toggleHintShow}
+
+ `;
+ item.insertAdjacentHTML("beforebegin", detailsBlock);
+
+ // Now move the toggle-able content inside of the details block
+ details = item.previousElementSibling
+ details.appendChild(item)
+ item.classList.add("toggle-details__container")
+
+ // Set up a click trigger to change the text as needed
+ details.addEventListener('click', (click) => {
+ let parent = click.target.parentElement;
+ if (parent.tagName.toLowerCase() == "details") {
+ summary = parent.querySelector("summary");
+ details = parent;
+ } else {
+ summary = parent;
+ details = parent.parentElement;
+ }
+ // Update the inner text for the proper hint
+ if (details.open) {
+ summary.querySelector("span.toggle-details__summary-text").innerText = toggleHintShow;
+ } else {
+ summary.querySelector("span.toggle-details__summary-text").innerText = toggleHintHide;
+ }
+
+ });
+
+ // If we have a toggle-shown class, open details block should be open
+ if (item.classList.contains("toggle-shown")) {
+ details.click();
+ }
+ }
+ })
+};
+
+// This should simply add / remove the collapsed class and change the button text
+var toggleHidden = (button) => {
+ target = button.dataset['target']
+ var itemToToggle = document.getElementById(target);
+ if (itemToToggle.classList.contains("toggle-hidden")) {
+ itemToToggle.classList.remove("toggle-hidden");
+ button.classList.remove("toggle-button-hidden");
+ } else {
+ itemToToggle.classList.add("toggle-hidden");
+ button.classList.add("toggle-button-hidden");
+ }
+}
+
+var toggleClickHandler = (click) => {
+ // Be cause the admonition title is clickable and extends to the whole admonition
+ // We only look for a click event on this title to trigger the toggle.
+
+ if (click.target.classList.contains("admonition-title")) {
+ button = click.target.querySelector(".toggle-button");
+ } else if (click.target.classList.contains("tb-icon")) {
+ // We've clicked the icon and need to search up one parent for the button
+ button = click.target.parentElement;
+ } else if (click.target.tagName == "polyline") {
+ // We've clicked the SVG elements inside the button, need to up 2 layers
+ button = click.target.parentElement.parentElement;
+ } else if (click.target.classList.contains("toggle-button")) {
+ // We've clicked the button itself and so don't need to do anything
+ button = click.target;
+ } else {
+ console.log(`[togglebutton]: Couldn't find button for ${click.target}`)
+ }
+ target = document.getElementById(button.dataset['button']);
+ toggleHidden(target);
+}
+
+// If we want to blanket-add toggle classes to certain cells
+var addToggleToSelector = () => {
+ const selector = "";
+ if (selector.length > 0) {
+ document.querySelectorAll(selector).forEach((item) => {
+ item.classList.add("toggle");
+ })
+ }
+}
+
+// Helper function to run when the DOM is finished
+const sphinxToggleRunWhenDOMLoaded = cb => {
+ if (document.readyState != 'loading') {
+ cb()
+ } else if (document.addEventListener) {
+ document.addEventListener('DOMContentLoaded', cb)
+ } else {
+ document.attachEvent('onreadystatechange', function() {
+ if (document.readyState == 'complete') cb()
+ })
+ }
+}
+sphinxToggleRunWhenDOMLoaded(addToggleToSelector)
+sphinxToggleRunWhenDOMLoaded(initToggleItems)
+
+/** Toggle details blocks to be open when printing */
+if (toggleOpenOnPrint == "true") {
+ window.addEventListener("beforeprint", () => {
+ // Open the details
+ document.querySelectorAll("details.toggle-details").forEach((el) => {
+ el.dataset["togglestatus"] = el.open;
+ el.open = true;
+ });
+
+ // Open the admonitions
+ document.querySelectorAll(".admonition.toggle.toggle-hidden").forEach((el) => {
+ console.log(el);
+ el.querySelector("button.toggle-button").click();
+ el.dataset["toggle_after_print"] = "true";
+ });
+ });
+ window.addEventListener("afterprint", () => {
+ // Re-close the details that were closed
+ document.querySelectorAll("details.toggle-details").forEach((el) => {
+ el.open = el.dataset["togglestatus"] == "true";
+ delete el.dataset["togglestatus"];
+ });
+
+ // Re-close the admonition toggle buttons
+ document.querySelectorAll(".admonition.toggle").forEach((el) => {
+ if (el.dataset["toggle_after_print"] == "true") {
+ el.querySelector("button.toggle-button").click();
+ delete el.dataset["toggle_after_print"];
+ }
+ });
+ });
+}
diff --git a/branch/main/_images/8-fair-principles.jpg b/branch/main/_images/8-fair-principles.jpg
new file mode 100644
index 0000000..efdfddb
Binary files /dev/null and b/branch/main/_images/8-fair-principles.jpg differ
diff --git a/branch/main/_images/add-suggestion.png b/branch/main/_images/add-suggestion.png
new file mode 100644
index 0000000..0a28544
Binary files /dev/null and b/branch/main/_images/add-suggestion.png differ
diff --git a/branch/main/_images/all-checks-failed.png b/branch/main/_images/all-checks-failed.png
new file mode 100644
index 0000000..5175684
Binary files /dev/null and b/branch/main/_images/all-checks-failed.png differ
diff --git a/branch/main/_images/bare-repository.png b/branch/main/_images/bare-repository.png
new file mode 100644
index 0000000..45286f9
Binary files /dev/null and b/branch/main/_images/bare-repository.png differ
diff --git a/branch/main/_images/binder.jpg b/branch/main/_images/binder.jpg
new file mode 100644
index 0000000..d04c467
Binary files /dev/null and b/branch/main/_images/binder.jpg differ
diff --git a/branch/main/_images/branches.png b/branch/main/_images/branches.png
new file mode 100644
index 0000000..2464b1c
Binary files /dev/null and b/branch/main/_images/branches.png differ
diff --git a/branch/main/_images/button.jpg b/branch/main/_images/button.jpg
new file mode 100644
index 0000000..4a0d99e
Binary files /dev/null and b/branch/main/_images/button.jpg differ
diff --git a/branch/main/_images/chart.svg b/branch/main/_images/chart.svg
new file mode 100644
index 0000000..23cb3e9
--- /dev/null
+++ b/branch/main/_images/chart.svg
@@ -0,0 +1 @@
+label 0 1 −12 −10 −8 −6 −4 −2 0 2 4 6 8 10 12 x −12 −10 −8 −6 −4 −2 0 2 4 6 8 10 12 x false true correct Are predictions correct? (accuracy: 0.94) label 0 1 −12 −10 −8 −6 −4 −2 0 2 4 6 8 10 12 x −12 −10 −8 −6 −4 −2 0 2 4 6 8 10 12 x 0 1 label Training data
\ No newline at end of file
diff --git a/branch/main/_images/chatgpt.png b/branch/main/_images/chatgpt.png
new file mode 100644
index 0000000..6794801
Binary files /dev/null and b/branch/main/_images/chatgpt.png differ
diff --git a/branch/main/_images/check-details.png b/branch/main/_images/check-details.png
new file mode 100644
index 0000000..456a247
Binary files /dev/null and b/branch/main/_images/check-details.png differ
diff --git a/branch/main/_images/clone-address.png b/branch/main/_images/clone-address.png
new file mode 100644
index 0000000..abdbd85
Binary files /dev/null and b/branch/main/_images/clone-address.png differ
diff --git a/branch/main/_images/clone-of-fork.png b/branch/main/_images/clone-of-fork.png
new file mode 100644
index 0000000..e610a1a
Binary files /dev/null and b/branch/main/_images/clone-of-fork.png differ
diff --git a/branch/main/_images/clone.png b/branch/main/_images/clone.png
new file mode 100644
index 0000000..9e79c46
Binary files /dev/null and b/branch/main/_images/clone.png differ
diff --git a/branch/main/_images/code-completion.gif b/branch/main/_images/code-completion.gif
new file mode 100644
index 0000000..e0bed42
Binary files /dev/null and b/branch/main/_images/code-completion.gif differ
diff --git a/branch/main/_images/comment.png b/branch/main/_images/comment.png
new file mode 100644
index 0000000..6ae948c
Binary files /dev/null and b/branch/main/_images/comment.png differ
diff --git a/branch/main/_images/commit-suggestion.png b/branch/main/_images/commit-suggestion.png
new file mode 100644
index 0000000..4ec4bb6
Binary files /dev/null and b/branch/main/_images/commit-suggestion.png differ
diff --git a/branch/main/_images/create-repository-with-readme.png b/branch/main/_images/create-repository-with-readme.png
new file mode 100644
index 0000000..2411834
Binary files /dev/null and b/branch/main/_images/create-repository-with-readme.png differ
diff --git a/branch/main/_images/create-repository.png b/branch/main/_images/create-repository.png
new file mode 100644
index 0000000..80fb99c
Binary files /dev/null and b/branch/main/_images/create-repository.png differ
diff --git a/branch/main/_images/draft-pr-wip.png b/branch/main/_images/draft-pr-wip.png
new file mode 100644
index 0000000..12181d5
Binary files /dev/null and b/branch/main/_images/draft-pr-wip.png differ
diff --git a/branch/main/_images/draft-pr.png b/branch/main/_images/draft-pr.png
new file mode 100644
index 0000000..2099349
Binary files /dev/null and b/branch/main/_images/draft-pr.png differ
diff --git a/branch/main/_images/exercise.png b/branch/main/_images/exercise.png
new file mode 100644
index 0000000..ddfe435
Binary files /dev/null and b/branch/main/_images/exercise.png differ
diff --git a/branch/main/_images/files-changed.png b/branch/main/_images/files-changed.png
new file mode 100644
index 0000000..7b58a22
Binary files /dev/null and b/branch/main/_images/files-changed.png differ
diff --git a/branch/main/_images/fork-after-update.png b/branch/main/_images/fork-after-update.png
new file mode 100644
index 0000000..38fc8c6
Binary files /dev/null and b/branch/main/_images/fork-after-update.png differ
diff --git a/branch/main/_images/fork.png b/branch/main/_images/fork.png
new file mode 100644
index 0000000..50cd9cc
Binary files /dev/null and b/branch/main/_images/fork.png differ
diff --git a/branch/main/_images/forkandclone.png b/branch/main/_images/forkandclone.png
new file mode 100644
index 0000000..d050183
Binary files /dev/null and b/branch/main/_images/forkandclone.png differ
diff --git a/branch/main/_images/github-branches.png b/branch/main/_images/github-branches.png
new file mode 100644
index 0000000..111eca1
Binary files /dev/null and b/branch/main/_images/github-branches.png differ
diff --git a/branch/main/_images/github-compare-and-pr.png b/branch/main/_images/github-compare-and-pr.png
new file mode 100644
index 0000000..efca375
Binary files /dev/null and b/branch/main/_images/github-compare-and-pr.png differ
diff --git a/branch/main/_images/github-comparing-changes.png b/branch/main/_images/github-comparing-changes.png
new file mode 100644
index 0000000..3f6f718
Binary files /dev/null and b/branch/main/_images/github-comparing-changes.png differ
diff --git a/branch/main/_images/github-contribute.png b/branch/main/_images/github-contribute.png
new file mode 100644
index 0000000..ef80527
Binary files /dev/null and b/branch/main/_images/github-contribute.png differ
diff --git a/branch/main/_images/github-merged.png b/branch/main/_images/github-merged.png
new file mode 100644
index 0000000..fcd4404
Binary files /dev/null and b/branch/main/_images/github-merged.png differ
diff --git a/branch/main/_images/github-navigate-to-branch.png b/branch/main/_images/github-navigate-to-branch.png
new file mode 100644
index 0000000..9ac4731
Binary files /dev/null and b/branch/main/_images/github-navigate-to-branch.png differ
diff --git a/branch/main/_images/good-vs-bad.svg b/branch/main/_images/good-vs-bad.svg
new file mode 100644
index 0000000..e787bea
--- /dev/null
+++ b/branch/main/_images/good-vs-bad.svg
@@ -0,0 +1,191 @@
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+ impure
+ bad code
+
+
+ impure
+ better code
+ pure
+
+
+ impure
+ good code
+ pure
+
+
+
diff --git a/branch/main/_images/gophers.png b/branch/main/_images/gophers.png
new file mode 100644
index 0000000..741406b
Binary files /dev/null and b/branch/main/_images/gophers.png differ
diff --git a/branch/main/_images/history.png b/branch/main/_images/history.png
new file mode 100644
index 0000000..faecc66
Binary files /dev/null and b/branch/main/_images/history.png differ
diff --git a/branch/main/_images/issue-number.png b/branch/main/_images/issue-number.png
new file mode 100644
index 0000000..be273a2
Binary files /dev/null and b/branch/main/_images/issue-number.png differ
diff --git a/branch/main/_images/leave-comment.png b/branch/main/_images/leave-comment.png
new file mode 100644
index 0000000..95e33c2
Binary files /dev/null and b/branch/main/_images/leave-comment.png differ
diff --git a/branch/main/_images/license-models.png b/branch/main/_images/license-models.png
new file mode 100644
index 0000000..f413617
Binary files /dev/null and b/branch/main/_images/license-models.png differ
diff --git a/branch/main/_images/license.png b/branch/main/_images/license.png
new file mode 100644
index 0000000..ec5cf85
Binary files /dev/null and b/branch/main/_images/license.png differ
diff --git a/branch/main/_images/merging.png b/branch/main/_images/merging.png
new file mode 100644
index 0000000..b954afb
Binary files /dev/null and b/branch/main/_images/merging.png differ
diff --git a/branch/main/_images/network.png b/branch/main/_images/network.png
new file mode 100644
index 0000000..96b97ae
Binary files /dev/null and b/branch/main/_images/network.png differ
diff --git a/branch/main/_images/new-repository.png b/branch/main/_images/new-repository.png
new file mode 100644
index 0000000..aff7677
Binary files /dev/null and b/branch/main/_images/new-repository.png differ
diff --git a/branch/main/_images/owl.png b/branch/main/_images/owl.png
new file mode 100644
index 0000000..7f90e9f
Binary files /dev/null and b/branch/main/_images/owl.png differ
diff --git a/branch/main/_images/packages.jpg b/branch/main/_images/packages.jpg
new file mode 100644
index 0000000..421b4fc
Binary files /dev/null and b/branch/main/_images/packages.jpg differ
diff --git a/branch/main/_images/pull-request-form.png b/branch/main/_images/pull-request-form.png
new file mode 100644
index 0000000..c6c6af8
Binary files /dev/null and b/branch/main/_images/pull-request-form.png differ
diff --git a/branch/main/_images/record-player.png b/branch/main/_images/record-player.png
new file mode 100644
index 0000000..467c838
Binary files /dev/null and b/branch/main/_images/record-player.png differ
diff --git a/branch/main/_images/sharing.png b/branch/main/_images/sharing.png
new file mode 100644
index 0000000..8368d6b
Binary files /dev/null and b/branch/main/_images/sharing.png differ
diff --git a/branch/main/_images/sync-fork.png b/branch/main/_images/sync-fork.png
new file mode 100644
index 0000000..848a054
Binary files /dev/null and b/branch/main/_images/sync-fork.png differ
diff --git a/branch/main/_images/testing-jupyter1.png b/branch/main/_images/testing-jupyter1.png
new file mode 100644
index 0000000..38bd6cc
Binary files /dev/null and b/branch/main/_images/testing-jupyter1.png differ
diff --git a/branch/main/_images/testing-jupyter2.png b/branch/main/_images/testing-jupyter2.png
new file mode 100644
index 0000000..53c541e
Binary files /dev/null and b/branch/main/_images/testing-jupyter2.png differ
diff --git a/branch/main/_images/testing-jupyter3.png b/branch/main/_images/testing-jupyter3.png
new file mode 100644
index 0000000..1b1e47a
Binary files /dev/null and b/branch/main/_images/testing-jupyter3.png differ
diff --git a/branch/main/_images/testing-jupyter4.png b/branch/main/_images/testing-jupyter4.png
new file mode 100644
index 0000000..25c7795
Binary files /dev/null and b/branch/main/_images/testing-jupyter4.png differ
diff --git a/branch/main/_images/testing-jupyter5.png b/branch/main/_images/testing-jupyter5.png
new file mode 100644
index 0000000..c8d3491
Binary files /dev/null and b/branch/main/_images/testing-jupyter5.png differ
diff --git a/branch/main/_images/turntable.png b/branch/main/_images/turntable.png
new file mode 100644
index 0000000..38829e7
Binary files /dev/null and b/branch/main/_images/turntable.png differ
diff --git a/branch/main/_images/unwatch.png b/branch/main/_images/unwatch.png
new file mode 100644
index 0000000..020a6c9
Binary files /dev/null and b/branch/main/_images/unwatch.png differ
diff --git a/branch/main/_images/upload-files.png b/branch/main/_images/upload-files.png
new file mode 100644
index 0000000..fd3ef8c
Binary files /dev/null and b/branch/main/_images/upload-files.png differ
diff --git a/branch/main/_images/vscode-authorize.png b/branch/main/_images/vscode-authorize.png
new file mode 100644
index 0000000..4d45dc2
Binary files /dev/null and b/branch/main/_images/vscode-authorize.png differ
diff --git a/branch/main/_images/vscode-publish-branch.png b/branch/main/_images/vscode-publish-branch.png
new file mode 100644
index 0000000..323ffb2
Binary files /dev/null and b/branch/main/_images/vscode-publish-branch.png differ
diff --git a/branch/main/_images/vscode-publish-to-github1.png b/branch/main/_images/vscode-publish-to-github1.png
new file mode 100644
index 0000000..883e756
Binary files /dev/null and b/branch/main/_images/vscode-publish-to-github1.png differ
diff --git a/branch/main/_images/vscode-publish-to-github2.png b/branch/main/_images/vscode-publish-to-github2.png
new file mode 100644
index 0000000..d0055db
Binary files /dev/null and b/branch/main/_images/vscode-publish-to-github2.png differ
diff --git a/branch/main/_images/vscode-publish-to-github3.png b/branch/main/_images/vscode-publish-to-github3.png
new file mode 100644
index 0000000..0f68d0e
Binary files /dev/null and b/branch/main/_images/vscode-publish-to-github3.png differ
diff --git a/branch/main/_images/vscode-start.png b/branch/main/_images/vscode-start.png
new file mode 100644
index 0000000..e783080
Binary files /dev/null and b/branch/main/_images/vscode-start.png differ
diff --git a/branch/main/_sources/collaboration.md.txt b/branch/main/_sources/collaboration.md.txt
new file mode 100644
index 0000000..6b7515d
--- /dev/null
+++ b/branch/main/_sources/collaboration.md.txt
@@ -0,0 +1,10 @@
+# Collaborative version control and code review
+
+```{toctree}
+:maxdepth: 1
+
+collaboration/concepts.md
+collaboration/same-repository.md
+collaboration/code-review.md
+collaboration/forking-workflow.md
+```
diff --git a/branch/main/_sources/collaboration/code-review.md.txt b/branch/main/_sources/collaboration/code-review.md.txt
new file mode 100644
index 0000000..6b70b19
--- /dev/null
+++ b/branch/main/_sources/collaboration/code-review.md.txt
@@ -0,0 +1,142 @@
+# Practicing code review
+
+In this episode we will practice the code review process. We will learn how to
+ask for changes in a pull request, how to suggest a change in a pull request,
+and how to modify a pull request.
+
+This will enable research groups to work more collaboratively and to not only
+improve the code quality but also to **learn from each other**.
+
+
+## Exercise
+
+:::{prereq} Exercise preparation
+We can continue in the same exercise repository which we have used in the
+previous episode.
+:::
+
+:::{exercise} Exercise: Practicing code review (25 min)
+
+**Technical requirements**:
+- If you create the commits locally: [Being able to authenticate to GitHub](https://coderefinery.github.io/installation/ssh/)
+
+**What is familiar** from previous lessons:
+- Creating a branch.
+- Committing a change on the new branch.
+- Opening and merging pull requests.
+
+**What will be new** in this exercise:
+- As a reviewer, we will learn how to ask for changes in a pull request.
+- As a reviewer, we will learn how to suggest a change in a pull request.
+- As a submitter, we will learn how to modify a pull request without closing
+ the incomplete one and opening a new one.
+
+**Exercise tasks**:
+1. Create a new branch and one or few commits: in these improve something but also
+ deliberately introduce a typo and also a larger mistake which we will want to fix during the code review.
+1. Open a pull request towards the main branch.
+1. As a reviewer to somebody else's pull request, ask for an improvement and
+ also directly suggest a change for the small typo. (Hint:
+ suggestions are possible through the GitHub web interface, view of
+ a pull request, "Files changed" view, after selecting some lines.
+ Look for the "±" button.)
+1. As the submitter, learn how to accept the suggested change. (Hint:
+ GitHub web interface, "Files Changed" view.)
+1. As the submitter, improve the pull request without having to close and open
+ a new one: by adding a new commit to the same branch. (Hint: push
+ to the branch again.)
+1. Once the changes are addressed, merge the pull request.
+:::
+
+
+## Help and discussion
+
+From here on out, we don't give detailed steps to the solution. You
+need to combine what you know, and the extra info below, in order to
+solve the above.
+
+### How to ask for changes in a pull request
+
+Technically, there are at least two common ways to ask for changes in a pull
+request.
+
+Either in the comment field of the pull request:
+:::{figure} img/code-review/comment.png
+:width: 60%
+:class: with-border
+:alt: Screenshot of a pull request comment field
+::::
+
+Or by using the "Review changes":
+:::{figure} img/code-review/files-changed.png
+:width: 100%
+:class: with-border
+:alt: Screenshot of a pull request navigating to the "Review changes" tab
+::::
+
+And always please be kind and constructive in your comments. Remember that the
+goal is not gate-keeping but **collaborative learning**.
+
+
+### How to suggest a change in a pull request as a reviewer
+
+If you see a very small problem that is easy to fix, you can suggest a change
+as a reviewer.
+
+Instead of asking the submitter to tiny problem, you can suggest a change by
+clicking on the plus sign next to the line number in the "Files changed" tab:
+:::{figure} img/code-review/leave-comment.png
+:width: 100%
+:class: with-border
+:alt: Screenshot of leaving a comment to a line in a pull request
+::::
+
+Here you can comment on specific lines or even line ranges.
+
+But now the interesting part is to click on the "Add a suggestion" symbol (the
+one that looks like plus and minus). Now you can fix the tiny problem (in this
+case a typo) and then click on the "Add single comment" button:
+:::{figure} img/code-review/add-suggestion.png
+:width: 60%
+:class: with-border
+:alt: Sequence of clicks to add a suggestion to a line in a pull request
+::::
+
+The result is this and the submitter can accept the change with a single click:
+:::{figure} img/code-review/commit-suggestion.png
+:width: 60%
+:class: with-border
+:alt: Screenshot of a pull request with a suggested change
+::::
+
+After accepting with "Commit suggestion", the improvement gets added to the
+pull request.
+
+
+### How to modify a pull request to address the review comments
+
+If the reviewer asks for changes, it is not necessary to close the pull request
+and later open a new one. It can even be counter-productive to do so: This can
+fragment the discussion and the history of the pull request and can make it
+harder to understand the context of the changes.
+
+A much better mechanism is to recognize that pull requests are not implemented
+from a specific commit to a specific branch, but **always from a branch to a
+branch**.
+
+This means that you can make amendments to the pull request by adding new
+commits to the same source branch. This way the pull request will be updated
+automatically and the reviewer can see the new changes and comment on them.
+
+The fact that pull requests are from branch to branch also strongly suggests
+that it is a good practice to **create a new branch for each pull request**.
+Otherwise you could accidentally modify an open pull request by adding new
+commits to the source branch.
+
+
+## Summary
+
+- Our process isn't just about code now. It's about discussion and
+ working together to make the whole process better.
+- GitHub (or GitLab) discussions and reviewing are quite powerful and can make
+ small changes easy.
diff --git a/branch/main/_sources/collaboration/concepts.md.txt b/branch/main/_sources/collaboration/concepts.md.txt
new file mode 100644
index 0000000..aa48c04
--- /dev/null
+++ b/branch/main/_sources/collaboration/concepts.md.txt
@@ -0,0 +1,67 @@
+# Concepts around collaboration
+
+```{objectives}
+- Be able to decide whether to divide work at the branch level or at the repository level.
+```
+
+
+## Commits, branches, repositories, forks, clones
+
+- **repository**: The project, contains all data and history (commits, branches, tags).
+- **commit**: Snapshot of the project, gets a unique identifier (e.g. `c7f0e8bfc718be04525847fc7ac237f470add76e`).
+- **branch**: Independent development line. The main development line is often called `main`.
+- **tag**: A pointer to one commit, to be able to refer to it later. Like a [commemorative plaque](https://en.wikipedia.org/wiki/Commemorative_plaque)
+ that you attach to a particular commit (e.g. `phd-printed` or `paper-submitted`).
+- **cloning**: Copying the whole repository to your laptop - the first time. It is not necessary to download each file one by one.
+- **forking**: Taking a copy of a repository (which is typically not yours) - your
+ copy (fork) stays on GitHub/GitLab and you can make changes to your copy.
+
+
+## Cloning a repository
+
+In order to make a complete copy a whole repository, the `git clone` command
+can be used. When cloning, all the files, of all or selected branches, of a
+repository are copied in one operation. Cloning of a repository is of relevance
+in a few different situations:
+* Working on your own, cloning is the operation that you can use to create
+ multiple instances of a repository on, for instance, a personal computer, a
+ server, and a supercomputer.
+* The parent repository could be a repository that you or your colleague own. A
+ common use case for cloning is when working together within a smaller team
+ where everyone has read and write access to the same git repository.
+* Alternatively, cloning can be made from a public repository of a code that
+ you would like to use. Perhaps you have no intention to work on the code, but
+ would like to stay in tune with the latest developments, also in-between
+ releases of new versions of the code.
+
+
+```{figure} img/overview/forkandclone.png
+:alt: Forking and cloning
+:width: 100%
+:class: with-border
+
+Forking and cloning
+```
+
+
+## Forking a repository
+
+When a fork is made on GitHub/GitLab a complete copy, of all or selected
+branches, of the repository is made. The copy will reside under a different
+account on GitHub/GitLab. Forking of a repository is of high relevance when
+working with a git repository to which you do not have write access.
+* In the fork repository commits can be made to the base branch (`main` or
+ `master`), and to other branches.
+* The commits that are made within the branches of the fork repository can be
+ contributed back to the parent repository by means of pull or merge requests.
+
+
+## Synchronizing changes between repositories
+
+- We need a mechanism to communicate changes between the repositories.
+- We will **pull** or **fetch** updates **from** remote repositories (we will soon discuss the difference between pull and fetch).
+- We will **push** updates **to** remote repositories.
+- We will learn how to suggest changes within repositories on GitHub and across repositories (**pull request**).
+- Repositories that are forked or cloned do not automatically synchronize themselves:
+ We will learn how to update forks (by pulling from the "central" repository).
+- A main difference between cloning a repository and forking a repository is that the former is a general operation for generating copies of a repository to different computers, whereas forking is a particular operation implemented on GitHub/GitLab.
diff --git a/branch/main/_sources/collaboration/forking-workflow.md.txt b/branch/main/_sources/collaboration/forking-workflow.md.txt
new file mode 100644
index 0000000..1c1b0a7
--- /dev/null
+++ b/branch/main/_sources/collaboration/forking-workflow.md.txt
@@ -0,0 +1,221 @@
+# How to contribute changes to repositories that belong to others
+
+In this episode we prepare you to suggest and contribute changes to
+repositories that belong to others. These might be open source projects that
+you use in your work.
+
+We will see how Git and services like GitHub or GitLab can be used to suggest
+modification without having to ask for write access to the repository and
+accept modifications without having to grant write access to others.
+
+
+## Exercise
+
+:::{prereq} Exercise preparation
+- The exercise repository is now different:
+ (note the **-forking-exercise**).
+- First **fork** the exercise repository to your GitHub account.
+- Then **clone your fork** to your computer (if you wish to work locally).
+- Double-check that you have forked the correct repository.
+:::
+
+:::{exercise} Exercise: Collaborating within the same repository (25 min)
+
+**Technical requirements**:
+- If you create the commits locally: [Being able to authenticate to GitHub](https://coderefinery.github.io/installation/ssh/)
+
+**What is familiar** from previous lessons:
+- Forking a repository.
+- Creating a branch.
+- Committing a change on the new branch.
+- Opening and merging pull requests.
+
+**What will be new** in this exercise:
+- Opening a pull request towards the upstream repository.
+- Pull requests can be coupled with automated testing.
+- Learning that your fork can get out of date.
+- After the pull requests are merged, updating your fork with the changes.
+- Learn how to approach other people's repositories with ideas, changes, and requests.
+
+**Exercise tasks**:
+1. Open an issue in the upstream exercise repository where you describe the
+ change you want to make. Take note of the issue number.
+1. Create a new branch in your fork of the repository.
+1. Make a change to the recipe book on the new branch and in the commit cross-reference the issue you opened.
+ See the walk-through below for how to do this.
+1. Open a pull request towards the upstream repository.
+1. The instructor will review and merge the pull requests.
+ During the review, pay attention to the automated test step (here for
+ demonstration purposes, we test whether the recipe contains an ingredients
+ and an instructions sections).
+1. After few pull requests are merged, update your fork with the changes.
+1. Check that in your fork you can see changes from other people's pull requests.
+:::
+
+
+## Help and discussion
+
+
+### Help! I don't have permissions to push my local changes
+
+Maybe you see an error like this one:
+```text
+Please make sure you have the correct access rights
+and the repository exists.
+```
+
+Or like this one:
+```text
+failed to push some refs to workshop-material/recipe-book-forking-exercise.git
+```
+
+In this case you probably try to push the changes not to your fork but to the original repository
+and in this exercise you do not have write access to the original repository.
+
+The simpler solution is to clone again but this time your fork.
+
+:::{solution} Recovery
+
+But if you want to keep your local changes, you can change the remote URL to point to your fork.
+Check where your remote points to with `git remote --verbose`.
+
+It should look like this (replace `USER` with your GitHub username):
+```console
+$ git remote --verbose
+
+origin git@github.com:USER/recipe-book-forking-exercise.git (fetch)
+origin git@github.com:USER/recipe-book-forking-exercise.git (push)
+```
+
+It should **not** look like this:
+```console
+$ git remote --verbose
+
+origin git@github.com:workshop-material/recipe-book-forking-exercise.git (fetch)
+origin git@github.com:workshop-material/recipe-book-forking-exercise.git (push)
+```
+
+In this case you can adjust "origin" to point to your fork with:
+```console
+$ git remote set-url origin git@github.com:USER/recipe-book-forking-exercise.git
+```
+:::
+
+
+### Opening a pull request towards the upstream repository
+
+We have learned in the previous episode that pull requests are always from
+branch to branch. But the branch can be in a different repository.
+
+When you open a pull request in a fork, by default GitHub will suggest to
+direct it towards the default branch of the upstream repository.
+
+This can be changed and it should always be verified, but in this case this is
+exactly what we want to do, from fork towards upstream:
+:::{figure} img/forking-workflow/pull-request-form.png
+:width: 100%
+:class: with-border
+:alt: Screenshot of a pull request from fork towards upstream
+::::
+
+
+### Pull requests can be coupled with automated testing
+
+We added an automated test here just for fun and so that you see that this is
+possible to do.
+
+In this exercise, the test is silly. It will check whether the recipe contains
+both an ingredients and an instructions section.
+
+In this example the test failed:
+:::{figure} img/forking-workflow/all-checks-failed.png
+:width: 60%
+:class: with-border
+:alt: Screenshot of a failed test in a pull request
+::::
+
+Click on the "Details" link to see the details of the failed test:
+:::{figure} img/forking-workflow/check-details.png
+:width: 60%
+:class: with-border
+:alt: Screenshot of details why the test failed
+::::
+
+**How can this be useful?**
+- The project can define what kind of tests are expected to pass before a pull
+ request can be merged.
+- The reviewer can see the results of the tests, without having to run them
+ locally.
+
+**How does it work?**
+- We added a GitHub Actions workflow
+ to automatically run on each push or pull request towards the `main` branch.
+
+What tests or steps can you image for your project to run automatically with
+each pull request?
+
+
+### How to update your fork with changes from upstream
+
+This used to be difficult but now it is two mouse clicks.
+
+Navigate to your fork and notice how GitHub tells you that your fork is behind.
+In my case, it is 9 commits behind upstream. To fix this, click on "Sync fork"
+and then "Update branch":
+:::{figure} img/forking-workflow/sync-fork.png
+:width: 80%
+:class: with-border
+:alt: Screenshot on GitHub fork page showing that the fork is behind
+::::
+
+After the update my "branch is up to date" with the upstream repository:
+:::{figure} img/forking-workflow/fork-after-update.png
+:width: 80%
+:class: with-border
+:alt: Screenshot on GitHub after fork has been updated
+::::
+
+
+### How to approach other people’s repositories with ideas, changes, and requests
+
+**Contributing very minor changes**
+- Clone or fork+clone repository
+- Create a branch
+- Commit and push change
+- Open a pull request or merge request
+
+**If you observe an issue and have an idea how to fix it**
+- Open an issue in the repository you wish to contribute to
+- Describe the problem
+- If you have a suggestion on how to fix it, describe your suggestion
+- Possibly **discuss and get feedback**
+- If you are working on the fix, indicate it in the issue so that others know that somebody is working on it and who is working on it
+- Submit your fix as pull request or merge request which references/closes the issue
+
+:::{admonition} Motivation
+- **Inform others about an observed problem**
+- Make it clear whether this issue is up for grabs or already being worked on
+:::
+
+**If you have an idea for a new feature**
+- Open an issue in the repository you wish to contribute to
+- In the issue, write a short proposal for your suggested change or new feature
+- Motivate why and how you wish to do this
+- Also indicate where you are unsure and where you would like feedback
+- **Discuss and get feedback before you code**
+- Once you start coding, indicate that you are working on it
+- Once you are done, submit your new feature as pull request or merge request which references/closes the issue/proposal
+
+:::{admonition} Motivation
+- **Get agreement and feedback before writing 5000 lines of code** which might be rejected
+- If we later wonder why something was done, we have the issue/proposal as
+ reference and can read up on the reasoning behind a code change
+:::
+
+
+## Summary
+
+- This forking workflow lets you propose changes to repositories for
+ which **you have no write access**.
+- This is the way that much modern open-source software works.
+- You can now contribute to any project you can view.
diff --git a/branch/main/_sources/collaboration/same-repository.md.txt b/branch/main/_sources/collaboration/same-repository.md.txt
new file mode 100644
index 0000000..0d21766
--- /dev/null
+++ b/branch/main/_sources/collaboration/same-repository.md.txt
@@ -0,0 +1,278 @@
+# Collaborating within the same repository
+
+In this episode, we will learn how to collaborate within the same repository.
+We will learn how to cross-reference issues and pull requests, how to review
+pull requests, and how to use draft pull requests.
+
+This exercise will form a good basis for collaboration that is suitable for
+most research groups.
+
+:::{note}
+When you read or hear **pull request**, please think of a **change proposal**.
+:::
+
+
+## Exercise
+
+In this exercise, we will contribute to a repository via a **pull request**.
+This means that you propose some change, and then it is accepted (or not).
+
+::::::{prereq} Exercise preparation
+
+- **First we need to get access** to the [exercise repository](https://github.com/workshop-material/recipe-book) to which we will
+ contribute.
+ - Instructor collects GitHub usernames from learners and adds them as collaborators to the exercise repository
+ (Settings -> Collaborators and teams -> Manage access -> Add people).
+
+- **Don't forget to accept the invitation**
+ - Check
+ - Alternatively check the inbox for the email account you registered with
+ GitHub. GitHub emails you an invitation link, but if you don't receive it
+ you can go to your GitHub notifications in the top right corner. The
+ maintainer can also "copy invite link" and share it within the group.
+
+- **Watching and unwatching repositories**
+ - Now that you are a collaborator, you get notified about new issues and pull
+ requests via email.
+ - If you do not wish this, you can "unwatch" a repository (top of
+ the project page).
+ - However, we recommend watching repositories you are interested
+ in. You can learn things from experts just by watching the
+ activity that come through a popular project.
+ :::{figure} img/unwatch.png
+ :alt: Unwatching a repository
+
+ Unwatch a repository by clicking "Unwatch" in the repository view,
+ then "Participating and @mentions" - this way, you will get
+ notifications about your own interactions.
+ :::
+::::::
+
+:::{exercise} Exercise: Collaborating within the same repository (25 min)
+
+**Technical requirements** (from installation instructions):
+- If you create the commits locally: [Being able to authenticate to GitHub](https://coderefinery.github.io/installation/ssh/)
+
+**What is familiar** from the previous workshop day (not repeated here):
+- Cloning a repository.
+- Creating a branch.
+- Committing a change on the new branch.
+- Submit a pull request towards the main branch.
+
+**What will be new** in this exercise:
+- If you create the changes locally, you will need to **push** them to the remote repository.
+- Learning what a protected branch is and how to modify a protected branch: using a pull request.
+- Cross-referencing issues and pull requests.
+- Practice to review a pull request.
+- Learn about the value of draft pull requests.
+
+**Exercise tasks**:
+1. Start in the [exercise
+ repository](https://github.com/workshop-material/recipe-book) and open an
+ issue where you describe the change you want to make. Note down the issue
+ number since you will need it later.
+1. Create a new branch.
+1. Make a change to the recipe book on the new branch and in the commit
+ cross-reference the issue you opened (see the walk-through below
+ for how to do that).
+1. Push your new branch (with the new commit) to the repository you
+ are working on.
+1. Open a pull request towards the main branch.
+1. Review somebody else's pull request and give constructive feedback. Merge their pull request.
+1. Try to create a new branch with some half-finished work and open a draft
+ pull request. Verify that the draft pull request cannot be merged since it
+ is not meant to be merged yet.
+:::
+
+
+## Solution and hints
+
+### (1) Opening an issue
+
+This is done through the GitHub web interface. For example, you could
+give the name of the recipe you want to add (so that others don't add
+the same one). It is the "Issues" tab.
+
+
+### (2) Create a new branch.
+
+If on GitHub, you can make the branch in the web interface.
+
+
+### (3) Make a change adding the recipe
+
+Add a new file with the recipe in it. Commit the file. In the commit
+message, include the note about the issue number, saying that this
+will close that issue.
+
+
+#### Cross-referencing issues and pull requests
+
+Each issue and each pull request gets a number and you can cross-reference them.
+
+When you open an issue, note down the issue number (in this case it is `#2`):
+:::{figure} img/same-repository/issue-number.png
+:width: 60%
+:class: with-border
+:alt: Each issue gets a number
+:::
+
+You can reference this issue number in a commit message or in a pull request, like in this
+commit message:
+```text
+this is the new recipe; fixes #2
+```
+
+If you forget to do that in your commit message, you can also reference the issue
+in the pull request description. And instead of `fixes` you can also use `closes` or `resolves`
+or `fix` or `close` or `resolve` (case insensitive).
+
+Here are all the keywords that GitHub recognizes:
+
+
+Then observe what happens in the issue once your commit gets merged: it will
+automatically close the issue and create a link between the issue and the
+commit. This is very useful for tracking what changes were made in response to
+which issue and to know from when **until when precisely** the issue was open.
+
+
+### (4) Push to GitHub as a new branch
+
+Push the branch to the repository. You should end up with a branch
+visible in the GitHub web view.
+
+This is only necessary if you created the changes locally. If you created the
+changes directly on GitHub, you can skip this step.
+
+:::::{tabs}
+::::{group-tab} VS Code
+In VS Code, you can "publish the branch" to the remote repository by clicking
+the cloud icon in the bottom left corner of the window:
+:::{figure} img/same-repository/vscode-publish-branch.png
+:width: 60%
+:class: with-border
+:alt: Publishing a branch in VS Code
+:::
+::::
+
+::::{group-tab} Command line
+If you have a local branch `my-branch` and you want to push it to the remote
+and make it visible there, first verify what your remote is:
+```console
+$ git remote --verbose
+
+origin git@github.com:USER/recipe-book.git (fetch)
+origin git@github.com:USER/recipe-book.git (push)
+```
+
+In this case the remote is called `origin` and refers to the address
+git@github.com:USER/recipe-book.git. Both can be used
+interchangeably. Make sure it points to the right repository, ideally a
+repository that you can write to.
+
+Now that you have a remote, you can push your branch to it:
+```console
+$ git push origin my-branch
+```
+This will create a new branch on the remote repository with the same name as
+your local branch.
+
+You can also do this:
+```console
+$ git push --set-upstream origin my-branch
+```
+This will connect the local branch and the remote branch so that in the future
+you can just type `git push` and `git pull` without specifying the branch name
+(but this depends on your Git configuration).
+
+
+**Troubleshooting**
+
+If you don't have a remote yet, you can add it with (adjust `ADDRESS` to your repository address):
+```console
+$ git remote add origin ADDRESS
+```
+
+ADDRESS is the part that you copy from here:
+:::{figure} img/same-repository/clone-address.png
+:width: 60%
+:class: with-border
+:alt: Copying the clone address from GitHub
+:::
+
+If the remote points to the wrong place, you can change it with:
+```console
+$ git remote set-url origin NEWADDRESS
+```
+::::
+:::::
+
+
+### (5) Open a pull request towards the main branch
+
+This is done through the GitHub web interface.
+
+
+### (6) Reviewing pull requests
+
+You review through the GitHub web interface.
+
+Checklist for reviewing a pull request:
+- Be kind, on the other side is a human who has put effort into this.
+- Be constructive: if you see a problem, suggest a solution.
+- Towards which branch is this directed?
+- Is the title descriptive?
+- Is the description informative?
+- Scroll down to see commits.
+- Scroll down to see the changes.
+- If you get incredibly many changes, also consider the license or copyright
+ and ask where all that code is coming from.
+- Again, be kind and constructive.
+- Later we will learn how to suggest changes directly in the pull request.
+
+If someone is new, it's often nice to say something encouraging in the
+comments before merging (even if it's just "thanks"). If all is good
+and there's not much else to say, you could merge directly.
+
+
+### (7) Draft pull requests
+
+Try to create a draft pull request:
+:::{figure} img/same-repository/draft-pr.png
+:width: 60%
+:class: with-border
+:alt: Creating a draft pull request
+::::
+
+Verify that the draft pull request cannot be merged until it is marked as ready
+for review:
+:::{figure} img/same-repository/draft-pr-wip.png
+:width: 60%
+:class: with-border
+:alt: Draft pull request cannot be merged
+::::
+
+Draft pull requests can be useful for:
+- **Feedback**: You can open a pull request early to get feedback on your work without
+ signaling that it is ready to merge.
+- **Information**: They can help communicating to others that a change is coming up and in
+ progress.
+
+
+### What is a protected branch? And how to modify it?
+
+A protected branch on GitHub or GitLab is a branch that cannot (accidentally)
+deleted or force-pushed to. It is also possible to require that a branch cannot
+be directly pushed to or modified, but that changes must be submitted via a
+pull request.
+
+To protect a branch in your own repository, go to "Settings" -> "Branches".
+
+
+
+### Summary
+
+- We used all the same pieces that we've learned previously.
+- But we successfully contributed to a **collaborative project**!
+- The pull request allowed us to contribute without changing directly:
+ this is very good when it's not mainly our project.
diff --git a/branch/main/_sources/credit.md.txt b/branch/main/_sources/credit.md.txt
new file mode 100644
index 0000000..afa4e6d
--- /dev/null
+++ b/branch/main/_sources/credit.md.txt
@@ -0,0 +1,11 @@
+# Credit
+
+This lesson is a mashup of the following sources (all CC-BY):
+- (shares common history with this lesson)
+-
+
+The lesson uses the following example repository:
+-
+
+The classification task has replaced the "planets" example repository used in
+the original lesson.
diff --git a/branch/main/_sources/dependencies.md.txt b/branch/main/_sources/dependencies.md.txt
new file mode 100644
index 0000000..26b30f3
--- /dev/null
+++ b/branch/main/_sources/dependencies.md.txt
@@ -0,0 +1,365 @@
+# Reproducible environments and dependencies
+
+:::{objectives}
+- There are not many codes that have no dependencies.
+ How should we **deal with dependencies**?
+- We will focus on installing and managing dependencies in Python when using packages from PyPI and Conda.
+- We will not discuss how to distribute your code as a package.
+:::
+
+[This episode borrows from
+and ]
+
+Essential XKCD comics:
+- [xkcd - dependency](https://xkcd.com/2347/)
+- [xkcd - superfund](https://xkcd.com/1987/)
+
+
+## How to avoid: "It works on my machine 🤷"
+
+Use a **standard way** to list dependencies in your project:
+- Python: `requirements.txt` or `environment.yml`
+- R: `DESCRIPTION` or `renv.lock`
+- Rust: `Cargo.lock`
+- Julia: `Project.toml`
+- C/C++/Fortran: `CMakeLists.txt` or `Makefile` or `spack.yaml` or the module
+ system on clusters or containers
+- Other languages: ...
+
+
+## Two ecosystems: PyPI (The Python Package Index) and Conda
+
+:::{admonition} PyPI
+- **Installation tool:** `pip` or `uv` or similar
+- Traditionally used for Python-only packages or
+ for Python interfaces to external libraries. There are also packages
+ that have bundled external libraries (such as numpy).
+- **Pros:**
+ - Easy to use
+ - Package creation is easy
+- **Cons:**
+ - Installing packages that need external libraries can be complicated
+:::
+
+:::{admonition} Conda
+- **Installation tool:** `conda` or `mamba` or similar
+- Aims to be a more general package distribution tool
+ and it tries to provide not only the Python packages, but also libraries
+ and tools needed by the Python packages.
+- **Pros:**
+ - Quite easy to use
+ - Easier to manage packages that need external libraries
+ - Not only for Python
+- **Cons:**
+ - Package creation is harder
+:::
+
+
+## Conda ecosystem explained
+
+- [Anaconda](https://www.anaconda.com) is a distribution of conda packages
+ made by Anaconda Inc. When using Anaconda remember to check that your
+ situation abides with their licensing terms (see below).
+
+- Anaconda has recently changed its **licensing terms**, which affects its
+ use in a professional setting. This caused uproar among academia
+ and Anaconda modified their position in
+ [this article](https://www.anaconda.com/blog/update-on-anacondas-terms-of-service-for-academia-and-research).
+
+ Main points of the article are:
+ - conda (installation tool) and community channels (e.g. conda-forge)
+ are free to use.
+ - Anaconda repository and **Anaconda's channels in the community repository**
+ are free for universities and companies with fewer than 200 employees.
+ Non-university research institutions and national laboratories need
+ licenses.
+ - Miniconda is free, when it does not download Anaconda's packages.
+ - Miniforge is not related to Anaconda, so it is free.
+
+ For ease of use on sharing environment files, we recommend using
+ Miniforge to create the environments and using conda-forge as the main
+ channel that provides software.
+
+- Major repositories/channels:
+ - [Anaconda Repository](https://repo.anaconda.com)
+ houses Anaconda's own proprietary software channels.
+ - Anaconda's proprietary channels: `main`, `r`, `msys2` and `anaconda`.
+ These are sometimes called `defaults`.
+ - [conda-forge](https://conda-forge.org) is the largest open source
+ community channel. It has over 28k packages that include open-source
+ versions of packages in Anaconda's channels.
+
+
+## Tools and distributions for dependency management in Python
+
+- [Poetry](https://python-poetry.org): Dependency management and packaging.
+- [Pipenv](https://pipenv.pypa.io): Dependency management, alternative to Poetry.
+- [pyenv](https://github.com/pyenv/pyenv): If you need different Python versions for different projects.
+- [virtualenv](https://docs.python.org/3/library/venv.html): Tool to create isolated Python environments for PyPI packages.
+- [micropipenv](https://github.com/thoth-station/micropipenv): Lightweight tool to "rule them all".
+- [Conda](https://docs.conda.io): Package manager for Python and other languages maintained by Anaconda Inc.
+- [Miniconda](https://docs.anaconda.com/miniconda/): A "miniature" version of conda, maintained by Anaconda Inc. By default uses
+ Anaconda's channels. Check licensing terms when using these packages.
+- [Mamba](https://mamba.readthedocs.io): A drop in replacement for conda.
+ It used be much faster than conda due to better
+ dependency solver but nowadays conda
+ [also uses the same solver](https://conda.org/blog/2023-11-06-conda-23-10-0-release/).
+ It still has some UI improvements.
+- [Micromamba](https://mamba.readthedocs.io/en/latest/user_guide/micromamba.html): Tiny version of the Mamba package manager.
+- [Miniforge](https://github.com/conda-forge/miniforge): Open-source Miniconda alternative with
+ conda-forge as the default channel and optionally mamba as the default installer.
+- [Pixi](https://pixi.sh): Modern, super fast tool which can manage conda environments.
+- [uv](https://docs.astral.sh/uv/): Modern, super fast replacement for pip,
+ poetry, pyenv, and virtualenv. You can also switch between Python versions.
+
+
+## Best practice: Install dependencies into isolated environments
+
+- For each project, create a **separate environment**.
+- Don't install dependencies globally for all projects. Sooner or later, different projects will have conflicting dependencies.
+- Install them **from a file** which documents them at the same time
+ Install dependencies by first recording them in `requirements.txt` or
+ `environment.yml` and install using these files, then you have a trace
+ (we will practice this later below).
+
+:::{keypoints}
+If somebody asks you what dependencies you have in your project, you should be
+able to answer this question **with a file**.
+
+In Python, the two most common ways to do this are:
+- **requirements.txt** (for pip and virtual environments)
+- **environment.yml** (for conda and similar)
+
+You can export ("freeze") the dependencies from your current environment into these files:
+```bash
+# inside a conda environment
+$ conda env export --from-history > environment.yml
+
+# inside a virtual environment
+$ pip freeze > requirements.txt
+```
+:::
+
+
+## How to communicate the dependencies as part of a report/thesis/publication
+
+Each notebook or script or project which depends on libraries should come with
+either a `requirements.txt` or a `environment.yml`, unless you are creating
+and distributing this project as Python package.
+
+- Attach a `requirements.txt` or a `environment.yml` to your thesis.
+- Even better: Put `requirements.txt` or a `environment.yml` in your Git repository along your code.
+- Even better: Also [binderize](https://mybinder.org/) your analysis pipeline.
+
+
+## Containers
+
+- A container is like an **operating system inside a file**.
+- "Building a container": Container definition file (recipe) -> Container image
+- This can be used with [Apptainer](https://apptainer.org/)/
+ [SingularityCE](https://sylabs.io/singularity/).
+
+Containers offer the following advantages:
+- **Reproducibility**: The same software environment can be recreated on
+ different computers. They force you to know and **document all your dependencies**.
+- **Portability**: The same software environment can be run on different computers.
+- **Isolation**: The software environment is isolated from the host system.
+- "**Time travel**":
+ - You can run old/unmaintained software on new systems.
+ - Code that needs new dependencies which are not available on old systems can
+ still be run on old systems.
+
+
+## How to install dependencies into environments
+
+Now we understand a bit better why and how we installed dependencies
+for this course in the {doc}`installation`.
+
+We have used **Miniforge** and the long command we have used was:
+```console
+$ mamba env create -n course -f https://raw.githubusercontent.com/coderefinery/reproducible-python-ml/main/software/environment.yml
+```
+
+This command did two things:
+- Create a new environment with name "course" (specified by `-n`).
+- Installed all dependencies listed in the `environment.yml` file (specified by
+ `-f`), which we fetched directly from the web.
+ [Here](https://github.com/coderefinery/reproducible-python-ml/blob/main/software/environment.yml)
+ you can browse it.
+
+For your own projects:
+1. Start by writing an `environment.yml` of `requirements.txt` file. They look like this:
+:::::{tabs}
+ ::::{tab} environment.yml
+ :::{literalinclude} ../software/environment.yml
+ :language: yaml
+ :::
+ ::::
+
+ ::::{tab} requirements.txt
+ :::{literalinclude} ../software/requirements.txt
+ :::
+ ::::
+:::::
+
+2. Then set up an isolated environment and install the dependencies from the file into it:
+:::::{tabs}
+ ::::{group-tab} Miniforge
+ - Create a new environment with name "myenv" from `environment.yml`:
+ ```console
+ $ conda env create -n myenv -f environment.yml
+ ```
+ Or equivalently:
+ ```console
+ $ mamba env create -n myenv -f environment.yml
+ ```
+ - Activate the environment:
+ ```console
+ $ conda activate myenv
+ ```
+ - Run your code inside the activated virtual environment.
+ ```console
+ $ python example.py
+ ```
+ ::::
+
+ ::::{group-tab} Pixi
+ - Create `pixi.toml` from `environment.yml`:
+ ```console
+ $ pixi init --import environment.yml
+ ```
+ - Run your code inside the environment:
+ ```console
+ $ pixi run python example.py
+ ```
+ ::::
+
+ ::::{group-tab} Virtual environment
+ - Create a virtual environment by running (the second argument is the name
+ of the environment and you can change it):
+ ```console
+ $ python -m venv venv
+ ```
+ - Activate the virtual environment (how precisely depends on your operating
+ system and shell).
+ - Install the dependencies:
+ ```console
+ $ python -m pip install -r requirements.txt
+ ```
+ - Run your code inside the activated virtual environment.
+ ```console
+ $ python example.py
+ ```
+ ::::
+
+ ::::{group-tab} uv
+ - Create a virtual environment by running (the second argument is the name
+ of the environment and you can change it):
+ ```console
+ $ uv venv venv
+ ```
+ - Activate the virtual environment (how precisely depends on your operating
+ system and shell).
+ - Install the dependencies:
+ ```console
+ $ uv pip sync requirements.txt
+ ```
+ - Run your code inside the virtual environment.
+ ```console
+ $ uv run python example.py
+ ```
+ ::::
+:::::
+
+
+## Updating environments
+
+What if you forgot a dependency? Or during the development of your project
+you realize that you need a new dependency? Or you don't need some dependency anymore?
+
+1. Modify the `environment.yml` or `requirements.txt` file.
+2. Either remove your environment and create a new one, or update the existing one:
+
+:::::{tabs}
+ ::::{group-tab} Miniforge
+ - Update the environment by running:
+ ```console
+ $ conda env update --file environment.yml
+ ```
+ - Or equivalently:
+ ```console
+ $ mamba env update --file environment.yml
+ ```
+ ::::
+
+ ::::{group-tab} Pixi
+ - Remove `pixi.toml`.
+ - Then update it from the updated `environment.yml` by running:
+ ```console
+ $ pixi init --import environment.yml
+ ```
+ ::::
+
+ ::::{group-tab} Virtual environment
+ - Activate the virtual environment.
+ - Update the environment by running:
+ ```console
+ $ pip install -r requirements.txt
+ ```
+ ::::
+
+ ::::{group-tab} uv
+ - Activate the virtual environment.
+ - Update the environment by running:
+ ```console
+ $ uv pip sync requirements.txt
+ ```
+ ::::
+:::::
+
+
+## Pinning package versions
+
+Let us look at the
+[environment.yml](https://github.com/coderefinery/reproducible-python-ml/blob/main/software/environment.yml)
+which we used to set up the environment for this course.
+Dependencies are listed without version numbers. Should we **pin the
+versions**?
+
+- Both `pip` and `conda` ecosystems and all the tools that we have
+ mentioned support pinning versions.
+
+- It is possible to define a range of versions instead of precise versions.
+
+- While your project is still in progress, I often use latest versions and do not pin them.
+
+- When publishing the script or notebook, it is a good idea to pin the versions
+ to ensure that the code can be run in the future.
+
+- Remember that at some point in time you will face a situation where
+ newer versions of the dependencies are no longer compatible with your
+ software. At this point you'll have to update your software to use the newer
+ versions or to lock it into a place in time.
+
+
+## Managing dependencies on a supercomputer
+
+- Additional challenges:
+ - Storage quotas: **Do not install dependencies in your home directory**. A conda environment can easily contain 100k files.
+ - Network file systems struggle with many small files. Conda environments often contain many small files.
+- Possible solutions:
+ - Try [Pixi](https://pixi.sh/) (modern take on managing Conda environments) and
+ [uv](https://docs.astral.sh/uv/) (modern take on managing virtual
+ environments). Blog post: [Using Pixi and uv on a supercomputer](https://research-software.uit.no/blog/2025-pixi-and-uv/)
+ - Install your environment on the fly into a scratch directory on local disk (**not** the network file system).
+ - Install your environment on the fly into a RAM disk/drive.
+ - Containerize your environment into a container image.
+
+---
+
+:::{keypoints}
+- Being able to communicate your dependencies is not only nice for others, but
+ also for your future self or the next PhD student or post-doc.
+- If you ask somebody to help you with your code, they will ask you for the
+ dependencies.
+:::
diff --git a/branch/main/_sources/documentation.md.txt b/branch/main/_sources/documentation.md.txt
new file mode 100644
index 0000000..c2f8f62
--- /dev/null
+++ b/branch/main/_sources/documentation.md.txt
@@ -0,0 +1,397 @@
+# Where to start with documentation
+
+:::{objectives}
+- Discuss what makes good documentation.
+- Improve the README of your project or our example project.
+- Explore Sphinx which is a popular tool to build documentation websites.
+- Learn how to leverage GitHub Actions and GitHub Pages to build and deploy documentation.
+:::
+
+:::{instructor-note}
+- (30 min) Discussion
+- (30 min) Exercise: Set up a Sphinx documentation and add API documentation
+- (15 min) Demo: Building documentation with GitHub Actions
+:::
+
+
+## Why? 💗✉️ to your future self
+
+- You will probably use your code in the future and may forget details.
+- You may want others to use your code or contribute
+ (almost impossible without documentation).
+
+
+## In-code documentation
+
+Not very useful (more commentary than comment):
+```python
+# now we check if temperature is below -50
+if temperature < -50:
+ print("ERROR: temperature is too low")
+```
+
+More useful (explaining **why**):
+```python
+# we regard temperatures below -50 degrees as measurement errors
+if temperature < -50:
+ print("ERROR: temperature is too low")
+```
+
+Keeping zombie code "just in case" (rather use version control):
+```python
+# do not run this code!
+# if temperature > 0:
+# print("It is warm")
+```
+
+Emulating version control:
+```python
+# John Doe: threshold changed from 0 to 15 on August 5, 2013
+if temperature > 15:
+ print("It is warm")
+```
+
+
+## Many languages allow "docstrings"
+
+Example (Python):
+```python
+def kelvin_to_celsius(temp_k: float) -> float:
+ """
+ Converts temperature in Kelvin to Celsius.
+
+ Parameters
+ ----------
+ temp_k : float
+ temperature in Kelvin
+
+ Returns
+ -------
+ temp_c : float
+ temperature in Celsius
+ """
+ assert temp_k >= 0.0, "ERROR: negative T_K"
+
+ temp_c = temp_k - 273.15
+
+ return temp_c
+```
+
+:::{keypoints}
+- Documentation which is only in the source code is not enough.
+- Often a README is enough.
+- Documentation needs to be kept
+ **in the same Git repository** as the code since we want it to evolve with
+ the code.
+:::
+
+
+## Often a README is enough - checklist
+
+- **Purpose**
+- Requirements
+- Installation instructions
+- **Copy-paste-able example to get started**
+- Tutorials covering key functionality
+- Reference documentation (e.g. API) covering all functionality
+- Authors and **recommended citation**
+- License
+- Contribution guide
+
+See also the
+[JOSS review checklist](https://joss.readthedocs.io/en/latest/review_checklist.html).
+
+
+## Diátaxis
+
+Diátaxis is a systematic approach to technical documentation authoring.
+
+- Overview:
+- How to use Diátaxis **as a guide** to work:
+
+
+## What if you need more than a README?
+
+- Write documentation in
+ [Markdown (.md)](https://en.wikipedia.org/wiki/Markdown)
+ or
+ [reStructuredText (.rst)](https://en.wikipedia.org/wiki/ReStructuredText)
+ or
+ [R Markdown (.Rmd)](https://rmarkdown.rstudio.com/)
+
+- In the **same repository** as the code -> version control and **reproducibility**
+
+- Use one of many tools to build HTML out of md/rst/Rmd:
+ [Sphinx](https://sphinx-doc.org),
+ [MkDocs](https://www.mkdocs.org/),
+ [Zola](https://www.getzola.org/), [Jekyll](https://jekyllrb.com/),
+ [Hugo](https://gohugo.io/), RStudio, [knitr](https://yihui.org/knitr/),
+ [bookdown](https://bookdown.org/),
+ [blogdown](https://bookdown.org/yihui/blogdown/), ...
+
+- Deploy the generated HTML to [GitHub Pages](https://pages.github.com/) or
+ [GitLab Pages](https://docs.gitlab.com/ee/user/project/pages/)
+
+
+## Exercise: Set up a Sphinx documentation
+
+:::{prereq} Preparation
+In this episode we will use the following 5 packages which we installed
+previously as part of the {doc}`installation`:
+```
+myst-parser
+sphinx
+sphinx-rtd-theme
+sphinx-autoapi
+sphinx-autobuild
+```
+
+Which repository to use? You have 3 options:
+- Clone **your fork** of the example repository.
+- If you don't have that, you can clone the exercise repository itself.
+- You can try this with **your own project** and the project does not have to
+ be a Python project.
+:::
+
+There are at least two ways to get started with Sphinx:
+1. Use `sphinx-quickstart` to create a new Sphinx project.
+1. **This is what we will do instead**: Create three files (`doc/conf.py`, `doc/index.md`, and `doc/about.md`)
+ as starting point and improve from there.
+
+::::{exercise} Exercise: Set up a Sphinx documentation
+1. Create the following three files in your project:
+ ```
+ your-project/
+ ├── doc/
+ │ ├── conf.py
+ │ ├── index.md
+ │ └── about.md
+ └── ...
+ ```
+
+ This is `conf.py`:
+ ```python
+ project = "your-project"
+ copyright = "2025, Authors"
+ author = "Authors"
+ release = "0.1"
+
+ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
+
+ extensions = [
+ "myst_parser", # in order to use markdown
+ ]
+
+ myst_enable_extensions = [
+ "colon_fence", # ::: can be used instead of ``` for better rendering
+ ]
+
+ html_theme = "sphinx_rtd_theme"
+ ```
+
+ This is `index.md` (feel free to change the example text):
+ ```markdown
+ # Our code documentation
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
+ incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
+ nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+ fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
+ culpa qui officia deserunt mollit anim id est laborum.
+
+ :::{toctree}
+ :maxdepth: 2
+ :caption: Some caption
+
+ about.md
+ :::
+ ```
+
+ This is `about.md` (feel free to adjust):
+ ```markdown
+ # About this code
+
+ Work in progress ...
+ ```
+
+1. Run `sphinx-build` to build the HTML documentation:
+ ```console
+ $ sphinx-build doc _build
+
+ ... lots of output ...
+ The HTML pages are in _build.
+ ```
+
+1. Try to open `_build/index.html` in your browser.
+
+1. Experiment with adding more content, images, equations, code blocks, ...
+ - [typography](https://myst-parser.readthedocs.io/en/latest/syntax/typography.html)
+ - [images](https://myst-parser.readthedocs.io/en/latest/syntax/images_and_figures.html)
+ - [math and equations](https://myst-parser.readthedocs.io/en/latest/syntax/math.html)
+ - [code blocks](https://myst-parser.readthedocs.io/en/latest/syntax/code_and_apis.html)
+::::
+
+There is a lot more you can do:
+- This is useful if you want to check the integrity of all internal and external links:
+ ```console
+ $ sphinx-build doc -W -b linkcheck _build
+ ```
+- [sphinx-autobuild](https://pypi.org/project/sphinx-autobuild/)
+ provides a local web server that will automatically refresh your view
+ every time you save a file - which makes writing with live-preview much easier.
+
+
+## Demo: Building documentation with GitHub Actions
+
+:::{instructor-note}
+- Instructor presents.
+- Learners are encouraged to try this later on their own.
+:::
+
+First we need to extend the `environment.yml` file to include the necessary packages:
+```{code-block} yaml
+---
+emphasize-lines: 12-15
+---
+name: classification-task
+channels:
+ - conda-forge
+dependencies:
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - myst-parser
+ - sphinx
+ - sphinx-rtd-theme
+ - sphinx-autoapi
+```
+
+Then we add a GitHub Actions workflow `.github/workflow/sphinx.yml` to build the documentation:
+```{code-block} yaml
+---
+emphasize-lines: 31
+---
+name: Build documentation
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+permissions:
+ contents: write
+
+jobs:
+ docs:
+ runs-on: ubuntu-24.04
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - uses: mamba-org/setup-micromamba@v1
+ with:
+ micromamba-version: '2.0.5-0' # any version from https://github.com/mamba-org/micromamba-releases
+ environment-file: environment.yml
+ init-shell: bash
+ cache-environment: true
+ post-cleanup: 'all'
+ generate-run-shell: false
+
+ - name: Sphinx build
+ run: |
+ sphinx-build doc _build
+ shell: bash -el {0}
+
+ - name: Deploy to GitHub Pages
+ uses: peaceiris/actions-gh-pages@v4
+ if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
+ with:
+ publish_branch: gh-pages
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: _build/
+ force_orphan: true
+```
+
+Now:
+- Add these two changes to the GitHub repository.
+- Go to "Settings" -> "Pages" -> "Branch" -> `gh-pages` -> "Save".
+- Look at "Actions" tab and observe the workflow running and hopefully
+ deploying the website.
+- Finally visit the generated site. You can find it by clicking the About wheel
+ icon on top right of your repository. There, select "Use your GitHub Pages
+ website".
+- **This is how we build almost all of our lesson websites**,
+ including this one!
+- Another popular place to deploy Sphinx documentation is [ReadTheDocs](https://readthedocs.org/).
+
+
+## Optional: How to auto-generate API documentation in Python
+
+Add three tiny modifications (highlighted) to `doc/conf.py` to auto-generate API documentation
+(this requires the `sphinx-autoapi` package):
+```{code-block} python
+---
+emphasize-lines: 10, 14, 17
+---
+project = "your-project"
+copyright = "2025, Authors"
+author = "Authors"
+release = "0.1"
+
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
+
+extensions = [
+ "myst_parser", # in order to use markdown
+ "autoapi.extension", # in order to use markdown
+]
+
+# search this directory for Python files
+autoapi_dirs = [".."]
+
+# ignore this file when generating API documentation
+autoapi_ignore = ["*/conf.py"]
+
+myst_enable_extensions = [
+ "colon_fence", # ::: can be used instead of ``` for better rendering
+]
+
+html_theme = "sphinx_rtd_theme"
+```
+
+Then rebuild the documentation (or push the changes and let GitHub rebuild it)
+and you should see a new section "API Reference".
+
+
+## Possibilities to host Sphinx documentation
+
+- Build with [GitHub Actions](https://github.com/features/actions) and deploy to [GitHub Pages](https://pages.github.com/).
+- Build with [GitLab CI/CD](https://docs.gitlab.com/ee/ci/) and deploy to [GitLab Pages](https://docs.gitlab.com/ee/user/project/pages/).
+- Build with [Read the Docs](https://about.readthedocs.com/) and host there.
+
+
+## Confused about reStructuredText vs. Markdown vs. MyST?
+
+- At the beginning there was reStructuredText and Sphinx was built for reStructuredText.
+- Independently, Markdown was invented and evolved into a couple of flavors.
+- Markdown became more and more popular but was limited compared to reStructuredText.
+- Later, [MyST](https://myst-parser.readthedocs.io/en/latest/syntax/typography.html)
+ was invented to be able to write
+ something that looks like Markdown but in addition can do everything that
+ reStructuredText can do with extra directives.
+
+
+## Where to read more
+
+- [CodeRefinery documentation lesson](https://coderefinery.github.io/documentation/)
+- [Sphinx documentation](https://www.sphinx-doc.org/)
+- [Sphinx + ReadTheDocs guide](https://sphinx-rtd-tutorial.readthedocs.io/en/latest/index.html)
+- For more Markdown functionality, see the [Markdown guide](https://www.markdownguide.org/basic-syntax/).
+- For Sphinx additions, see [Sphinx Markup Constructs](https://www.sphinx-doc.org/en/master/markup/index.html).
+- [An opinionated guide on documentation in Python](https://docs.python-guide.org/writing/documentation/)
diff --git a/branch/main/_sources/example.md.txt b/branch/main/_sources/example.md.txt
new file mode 100644
index 0000000..6ce2757
--- /dev/null
+++ b/branch/main/_sources/example.md.txt
@@ -0,0 +1,96 @@
+# Example project: 2D classification task using a nearest-neighbor predictor
+
+The [example code](https://github.com/workshop-material/classification-task)
+that we will study is a relatively simple nearest-neighbor predictor written in
+Python. It is not important or expected that we understand the code in detail.
+
+The code will produce something like this:
+
+:::{figure} img/chart.svg
+:alt: Results of the classification task
+:width: 100%
+
+The bottom row shows the training data (two labels) and the top row shows the
+test data and whether the nearest-neighbor predictor classified their labels
+correctly.
+:::
+
+The **big picture** of the code is as follows:
+- We can choose the number of samples (the example above has 50 samples).
+- The code will generate samples with two labels (0 and 1) in a 2D space.
+- One of the labels has a normal distribution and a circular distribution with
+ some minimum and maximum radius.
+- The second label only has a circular distribution with a different radius.
+- Then we try to predict whether the test samples belong to label 0 or 1 based
+ on the nearest neighbors in the training data. The number of neighbors can
+ be adjusted and the code will take label of the majority of the neighbors.
+
+
+## Example run
+
+:::{instructor-note}
+The instructor demonstrates running the code on their computer.
+:::
+
+The code is written to accept **command-line arguments** to specify the number
+of samples and file names. Later we will discuss advantages of this approach.
+
+Let us try to get the help text:
+```console
+$ python generate_data.py --help
+
+Usage: generate_data.py [OPTIONS]
+
+ Program that generates a set of training and test samples for a non-linear
+ classification task.
+
+Options:
+ --num-samples INTEGER Number of samples for each class. [required]
+ --training-data TEXT Training data is written to this file. [required]
+ --test-data TEXT Test data is written to this file. [required]
+ --help Show this message and exit.
+```
+
+We first generate the training and test data:
+```console
+$ python generate_data.py --num-samples 50 --training-data train.csv --test-data test.csv
+
+Generated 50 training samples (train.csv) and test samples (test.csv).
+```
+
+In a second step we generate predictions for the test data:
+```console
+$ python generate_predictions.py --num-neighbors 7 --training-data train.csv --test-data test.csv --predictions predictions.csv
+
+Predictions saved to predictions.csv
+```
+
+Finally, we can plot the results:
+```console
+$ python plot_results.py --training-data train.csv --predictions predictions.csv --output-chart chart.svg
+
+Accuracy: 0.94
+Saved chart to chart.svg
+```
+
+
+## Discussion and goals
+
+:::{discussion}
+- Together we look at the generated files (train.csv, test.csv, predictions.csv, chart.svg).
+- We browse and discuss the [example code behind these scripts](https://github.com/workshop-material/classification-task).
+:::
+
+:::{admonition} Learning goals
+- What are the most important steps to make this code **reusable by others**
+ and **our future selves**?
+- Be able to apply these techniques to your own code/script.
+:::
+
+:::{admonition} We will not focus on ...
+- ... how the code works internally in detail.
+- ... whether this is the most efficient algorithm.
+- ... whether the code is numerically stable.
+- ... how to code scales with system size.
+- ... whether it is portable to other operating systems (we will discuss this later).
+:::
diff --git a/branch/main/_sources/good-practices.md.txt b/branch/main/_sources/good-practices.md.txt
new file mode 100644
index 0000000..840bfbd
--- /dev/null
+++ b/branch/main/_sources/good-practices.md.txt
@@ -0,0 +1,598 @@
+# Tools and useful practices
+
+:::{objectives}
+- How does good Python code look like? And if we only had 30 minutes, which
+ good practices should we highlight?
+- Some of the points are inspired by the excellent [Effective Python](https://effectivepython.com/) book by Brett Slatkin.
+:::
+
+
+## Follow the PEP 8 style guide
+
+- Please browse the [PEP 8 style guide](https://pep8.org/) so that you are familiar with the most important rules.
+- Using a consistent style makes your code easier to read and understand for others.
+- You don't have to check and adjust your code manually. There are tools that can do this for you (see below).
+
+
+## Linting and static type checking
+
+A **linter** is a tool that analyzes source code to detect potential errors, unused
+imports, unused variables, code style violations, and to improve readability.
+- Popular linters:
+ - [Autoflake](https://pypi.org/project/autoflake/)
+ - [Flake8](https://flake8.pycqa.org/)
+ - [Pyflakes](https://pypi.org/project/pyflakes/)
+ - [Pycodestyle](https://pycodestyle.pycqa.org/)
+ - [Pylint](https://pylint.readthedocs.io/)
+ - [Ruff](https://docs.astral.sh/ruff/)
+
+We recommend [Ruff](https://docs.astral.sh/ruff/) since it
+can do **both checking and formatting** and you don't have to switch between
+multiple tools.
+
+:::{discussion} Linters and formatters can be configured to your liking
+These tools typically have good defaults. But if you don't like the defaults,
+you can configure what they should ignore or how they should format or not format.
+:::
+
+This code example (which we possibly recognize from the previous section about
+{doc}`profiling`)
+has few problems (highlighted):
+```{code-block} python
+---
+emphasize-lines: 2, 7, 10
+---
+import re
+import requests
+
+
+def count_unique_words(file_path: str) -> int:
+ unique_words = set()
+ forgotten_variable = 13
+ with open(file_path, "r", encoding="utf-8") as file:
+ for line in file:
+ words = re.findall(r"\b\w+\b", line.lower()))
+ for word in words:
+ unique_words.add(word)
+ return len(unique_words)
+```
+
+Please try whether you can locate these problems using Ruff:
+```console
+$ ruff check
+```
+
+If you use version control and like to have your code checked or formatted
+**before you commit the change**, you can use tools like [pre-commit](https://pre-commit.com/).
+
+Many editors can be configured to automatically check your code as you type. Ruff can also
+be used as a **language server**.
+
+
+## Use an auto-formatter
+
+[Ruff](https://docs.astral.sh/ruff/) is one of the best tools to automatically
+format your code according to a consistent style.
+
+To demonstrate how it works, let us try to auto-format a code example which is badly formatted and also difficult
+to read:
+:::::{tabs}
+ ::::{tab} Badly formatted
+ ```python
+ import re
+ def count_unique_words (file_path : str)->int:
+ unique_words=set()
+ with open(file_path,"r",encoding="utf-8") as file:
+ for line in file:
+ words=re.findall(r"\b\w+\b",line.lower())
+ for word in words:
+ unique_words.add(word)
+ return len( unique_words )
+ ```
+ ::::
+
+ ::::{tab} Auto-formatted
+ ```python
+ import re
+
+
+ def count_unique_words(file_path: str) -> int:
+ unique_words = set()
+ with open(file_path, "r", encoding="utf-8") as file:
+ for line in file:
+ words = re.findall(r"\b\w+\b", line.lower())
+ for word in words:
+ unique_words.add(word)
+ return len(unique_words)
+ ```
+
+ This was done using:
+ ```console
+ $ ruff format
+ ```
+ ::::
+:::::
+
+Other popular formatters:
+ - [Black](https://black.readthedocs.io/)
+ - [YAPF](https://github.com/google/yapf)
+
+Many editors can be configured to automatically format for you when you save the file.
+
+It is possible to automatically format your code in Jupyter notebooks!
+For this to work you need
+the following three dependencies installed:
+```
+jupyterlab-code-formatter
+black
+isort
+```
+
+More information and a screen-cast of how this works can be found at
+ .
+
+
+## Consider annotating your functions with type hints
+
+Compare these two versions of the same function and discuss how the type hints
+can help you and the Python interpreter to understand the function better:
+:::::{tabs}
+ ::::{tab} Without type hints
+ ```{code-block} python
+ ---
+ emphasize-lines: 1
+ ---
+ def count_unique_words(file_path):
+ unique_words = set()
+ with open(file_path, "r", encoding="utf-8") as file:
+ for line in file:
+ words = re.findall(r"\b\w+\b", line.lower())
+ for word in words:
+ unique_words.add(word)
+ return len(unique_words)
+ ```
+ ::::
+ ::::{tab} With type hints
+ ```{code-block} python
+ ---
+ emphasize-lines: 1
+ ---
+ def count_unique_words(file_path: str) -> int:
+ unique_words = set()
+ with open(file_path, "r", encoding="utf-8") as file:
+ for line in file:
+ words = re.findall(r"\b\w+\b", line.lower())
+ for word in words:
+ unique_words.add(word)
+ return len(unique_words)
+ ```
+ ::::
+:::::
+
+A (static) type checker is a tool that checks whether the types of variables in your
+code match the types that you have specified.
+Popular tools:
+- [Mypy](https://mypy.readthedocs.io/)
+- [Pyright](https://github.com/microsoft/pyright) (Microsoft)
+- [Pyre](https://pyre-check.org/) (Meta)
+
+
+## Consider using AI-assisted coding
+
+We can use AI as an assistant/apprentice:
+- Code completion
+- Write a test based on an implementation
+- Write an implementation based on a test
+
+Or we can use AI as a mentor:
+- Explain a concept
+- Improve code
+- Show a different (possibly better) way of implementing the same thing
+
+
+:::{figure} img/productivity/chatgpt.png
+:alt: Screenshot of ChatGPT
+:width: 100%
+
+Example for using a chat-based AI tool.
+:::
+
+:::{figure} img/productivity/code-completion.gif
+:alt: Screen-cast of working with GitHub Copilot
+:width: 100%
+
+Example for using AI to complete code in an editor.
+:::
+
+:::{admonition} AI tools open up a box of questions which are beyond our scope here
+- Legal
+- Ethical
+- Privacy
+- Lock-in/ monopolies
+- Lack of diversity
+- Will we still need to learn programming?
+- How will it affect learning and teaching programming?
+:::
+
+
+## Debugging with print statements
+
+Print-debugging is a simple, effective, and popular way to debug your code like
+this:
+```python
+print(f"file_path: {file_path}")
+```
+Or more elaborate:
+```python
+print(f"I am in function count_unique_words and the value of file_path is {file_path}")
+```
+
+But there can be better alternatives:
+
+- [Logging](https://docs.python.org/3/library/logging.html) module
+ ```python
+ import logging
+
+ logging.basicConfig(level=logging.DEBUG)
+
+ logging.debug("This is a debug message")
+ logging.info("This is an info message")
+ ```
+- [IceCream](https://github.com/gruns/icecream) offers compact helper functions for print-debugging
+ ```python
+ from icecream import ic
+
+ ic(file_path)
+ ```
+
+
+## Often you can avoid using indices
+
+Especially people coming to Python from other languages tend to use indices
+where they are not needed. Indices can be error-prone (off-by-one errors and
+reading/writing past the end of the collection).
+
+### Iterating
+:::::{tabs}
+ ::::{tab} Verbose and can be brittle
+ ```python
+ scores = [13, 5, 2, 3, 4, 3]
+
+ for i in range(len(scores)):
+ print(scores[i])
+ ```
+ ::::
+
+ ::::{tab} Better
+ ```python
+ scores = [13, 5, 2, 3, 4, 3]
+
+ for score in scores:
+ print(score)
+ ```
+ ::::
+:::::
+
+
+### Enumerate if you need the index
+:::::{tabs}
+ ::::{tab} Verbose and can be brittle
+ ```python
+ particle_masses = [7.0, 2.2, 1.4, 8.1, 0.9]
+
+ for i in range(len(particle_masses)):
+ print(f"Particle {i} has mass {particle_masses[i]}")
+ ```
+ ::::
+
+ ::::{tab} Better
+ ```python
+ particle_masses = [7.0, 2.2, 1.4, 8.1, 0.9]
+
+ for i, mass in enumerate(particle_masses):
+ print(f"Particle {i} has mass {mass}")
+ ```
+ ::::
+:::::
+
+
+
+### Zip if you need to iterate over two collections
+
+:::::{tabs}
+ ::::{tab} Using an index can be brittle
+ ```python
+ persons = ["Alice", "Bob", "Charlie", "David", "Eve"]
+ favorite_ice_creams = ["vanilla", "chocolate", "strawberry", "mint", "chocolate"]
+
+ for i in range(len(persons)):
+ print(f"{persons[i]} likes {favorite_ice_creams[i]} ice cream")
+ ```
+ ::::
+
+ ::::{tab} Better
+ ```python
+ persons = ["Alice", "Bob", "Charlie", "David", "Eve"]
+ favorite_ice_creams = ["vanilla", "chocolate", "strawberry", "mint", "chocolate"]
+
+ for person, flavor in zip(persons, favorite_ice_creams):
+ print(f"{person} likes {flavor} ice cream")
+ ```
+ ::::
+:::::
+
+
+### Unpacking
+:::::{tabs}
+ ::::{tab} Verbose and can be brittle
+ ```python
+ coordinates = (0.1, 0.2, 0.3)
+
+ x = coordinates[0]
+ y = coordinates[1]
+ z = coordinates[2]
+ ```
+ ::::
+
+ ::::{tab} Better
+ ```python
+ coordinates = (0.1, 0.2, 0.3)
+
+ x, y, z = coordinates
+ ```
+ ::::
+:::::
+
+
+### Prefer catch-all unpacking over indexing/slicing
+
+:::::{tabs}
+ ::::{tab} Verbose and can be brittle
+ ```python
+ scores = [13, 5, 2, 3, 4, 3]
+
+ sorted_scores = sorted(scores)
+
+ smallest = sorted_scores[0]
+ rest = sorted_scores[1:-1]
+ largest = sorted_scores[-1]
+
+ print(smallest, rest, largest)
+ # Output: 2 [3, 3, 4, 5] 13
+ ```
+ ::::
+
+ ::::{tab} Better
+ ```python
+ scores = [13, 5, 2, 3, 4, 3]
+
+ sorted_scores = sorted(scores)
+
+ smallest, *rest, largest = sorted(scores)
+
+ print(smallest, rest, largest)
+ # Output: 2 [3, 3, 4, 5] 13
+ ```
+ ::::
+:::::
+
+
+### List comprehensions, map, and filter instead of loops
+
+:::::{tabs}
+ ::::{tab} For-loop
+ ```python
+ string_numbers = ["1", "2", "3", "4", "5"]
+
+ integer_numbers = []
+ for element in string_numbers:
+ integer_numbers.append(int(element))
+
+ print(integer_numbers)
+ # Output: [1, 2, 3, 4, 5]
+ ```
+ ::::
+
+ ::::{tab} List comprehension
+ ```python
+ string_numbers = ["1", "2", "3", "4", "5"]
+
+ integer_numbers = [int(element) for element in string_numbers]
+
+ print(integer_numbers)
+ # Output: [1, 2, 3, 4, 5]
+ ```
+ ::::
+
+ ::::{tab} Map
+ ```python
+ string_numbers = ["1", "2", "3", "4", "5"]
+
+ integer_numbers = list(map(int, string_numbers))
+
+ print(integer_numbers)
+ # Output: [1, 2, 3, 4, 5]
+ ```
+ ::::
+:::::
+
+:::::{tabs}
+ ::::{tab} For-loop
+ ```python
+ def is_even(number: int) -> bool:
+ return number % 2 == 0
+
+
+ numbers = [1, 2, 3, 4, 5, 6]
+
+ even_numbers = []
+ for number in numbers:
+ if is_even(number):
+ even_numbers.append(number)
+
+ print(even_numbers)
+ # Output: [2, 4, 6]
+ ```
+ ::::
+
+ ::::{tab} List comprehension
+ ```python
+ def is_even(number: int) -> bool:
+ return number % 2 == 0
+
+
+ numbers = [1, 2, 3, 4, 5, 6]
+
+ even_numbers = [number for number in numbers if is_even(number)]
+
+ print(even_numbers)
+ # Output: [2, 4, 6]
+ ```
+ ::::
+
+ ::::{tab} Filter
+ ```python
+ def is_even(number: int) -> bool:
+ return number % 2 == 0
+
+
+ numbers = [1, 2, 3, 4, 5, 6]
+
+ even_numbers = list(filter(is_even, numbers))
+
+ print(even_numbers)
+ # Output: [2, 4, 6]
+ ```
+ ::::
+:::::
+
+
+## Know your collections
+
+How to choose the right collection type:
+- Ordered and modifiable: `list`
+- Fixed and (rather) immutable: `tuple`
+- Key-value pairs: `dict`
+- Dictionary with default values: `defaultdict` from {py:mod}`collections`
+- Members are unique, no duplicates: `set`
+- Optimized operations at both ends: `deque` from {py:mod}`collections`
+- Cyclical iteration: `cycle` from {py:mod}`itertools`
+- Adding/removing elements in the middle: Create a linked list (e.g. using a dictionary or a dataclass)
+- Priority queue: {py:mod}`heapq` library
+- Search in sorted collections: {py:mod}`bisect` library
+
+What to avoid:
+- Need to add/remove elements at the beginning or in the middle? Don't use a list.
+- Need to make sure that elements are unique? Don't use a list.
+
+
+## Making functions more ergonomic
+
+- Less error-prone API functions and fewer backwards-incompatible changes by enforcing keyword-only arguments:
+ ```python
+ def send_message(*, message: str, recipient: str) -> None:
+ print(f"Sending to {recipient}: {message}")
+ ```
+
+- Use dataclasses or named tuples or dictionaries instead of too many input or output arguments.
+
+- Docstrings instead of comments:
+ ```python
+ def send_message(*, message: str, recipient: str) -> None:
+ """
+ Sends a message to a recipient.
+
+ Parameters:
+ - message (str): The content of the message.
+ - recipient (str): The name of the person receiving the message.
+ """
+ print(f"Sending to {recipient}: {message}")
+ ```
+
+- Consider using `DeprecationWarning` from the {py:mod}`warnings` module for deprecating functions or arguments.
+
+
+## Iterating
+
+- When working with large lists or large data sets, consider using generators or iterators instead of lists.
+ Discuss and compare these two:
+ ```python
+ even_numbers1 = [number for number in range(10000000) if number % 2 == 0]
+
+ even_numbers2 = (number for number in range(10000000) if number % 2 == 0)
+ ```
+
+- Beware of functions which iterate over the same collection multiple times.
+ With generators, you can iterate only once.
+
+- Know about {py:mod}`itertools` which provides a lot of functions for working with iterators.
+
+
+## Use relative paths and pathlib
+
+- Scripts that read data from absolute paths are not portable and typically
+ break when shared with a colleague or support help desk or reused by the next
+ student/PhD student/postdoc.
+- {py:mod}`pathlib` is a modern and portable way to handle paths in Python.
+
+
+## Project structure
+
+- As your project grows from a simple script, you should consider organizing
+ your code into modules and packages.
+
+- Function too long? Consider splitting it into multiple functions.
+
+- File too long? Consider splitting it into multiple files.
+
+- Difficult to name a function or file? It might be doing too much or unrelated things.
+
+- If your script can be imported into other scripts, wrap your main function in
+ a `if __name__ == "__main__":` block:
+ ```python
+ def main():
+ ...
+
+ if __name__ == "__main__":
+ main()
+ ```
+
+- Why this construct? You can try to either import or run the following script:
+ ```python
+ if __name__ == "__main__":
+ print("I am being run as a script") # importing will not run this part
+ else:
+ print("I am being imported")
+ ```
+
+- Try to have all code inside some function. This can make it easier to
+ understand, test, and reuse. It can also help Python to free up memory when
+ the function is done.
+
+
+## Reading and writing files
+
+- Good construct to know to read a file:
+ ```python
+ with open("input.txt", "r") as file:
+ for line in file:
+ print(line)
+ ```
+- Reading a huge data file? Read and process it in chunks or buffered or use a library which does it for you.
+- On supercomputers, avoid reading and writing thousands of small files.
+- For input files, consider using standard formats like CSV, YAML, or TOML - then you don't need to write a parser.
+
+
+## Use subprocess instead of os.system
+
+- Many things can go wrong when launching external processes from Python. The {py:mod}`subprocess` module is the recommended way to do this.
+- `os.system` is not portable and not secure enough.
+
+
+## Parallelizing
+
+- Use one of the many libraries: {py:mod}`multiprocessing`, {py:mod}`mpi4py`, [Dask](https://dask.org/), [Parsl](https://parsl-project.org/), ...
+- Identify independent tasks.
+- More often than not, you can convert an expensive loop into a command-line
+ tool and parallelize it using workflow management tools like
+ [Snakemake](https://snakemake.github.io/).
diff --git a/branch/main/_sources/index.md.txt b/branch/main/_sources/index.md.txt
new file mode 100644
index 0000000..2c80014
--- /dev/null
+++ b/branch/main/_sources/index.md.txt
@@ -0,0 +1,152 @@
+# Reproducible research software development using Python (ML example)
+
+
+## Big-picture goal
+
+This is a **hands-on course on research software engineering**. In this
+workshop we assume that most workshop participants use Python in their work or
+are leading a group which uses Python. Therefore, some of the examples will use
+Python as the example language.
+
+We will work with an example project ({doc}`example`)
+and go through all important steps of a typical
+software project. Once we have seen the building blocks, we will try to **apply
+them to own projects**.
+
+:::{prereq} Preparation
+1. Get a **GitHub account** following [these instructions](https://coderefinery.github.io/installation/github/).
+1. You will need a **text editor**. If you don't have a favorite one, we recommend
+ [VS Code](https://coderefinery.github.io/installation/vscode/).
+1. **If you prefer to work in the terminal** and not in VS Code, set up these two (skip this if you use VS Code):
+ - [Git in the terminal](https://coderefinery.github.io/installation/git-in-terminal/)
+ - [SSH or HTTPS connection to GitHub from terminal](https://coderefinery.github.io/installation/ssh/)
+1. Follow {doc}`installation` (but we will do this together at the beginning of the workshop).
+:::
+
+
+## Schedule
+
+### Monday
+
+- 09:00-10:00 - Getting started
+ - Welcome and introduction
+ - {doc}`installation`
+ - {doc}`example`
+
+- 10:15-11:30 - {doc}`version-control` (1/2)
+ - {doc}`version-control/motivation` (15 min)
+ - {doc}`version-control/browsing` (30 min)
+ - {doc}`version-control/branching-and-committing` (30 min)
+
+- 11:30-12:15 - Lunch break
+
+- 12:15-13:30 - {doc}`version-control` (2/2)
+ - {doc}`version-control/merging` (40 min)
+ - {doc}`version-control/conflict-resolution` (30 min)
+ - {doc}`version-control/practical-advice` (20 min)
+ - {doc}`version-control/sharing` (30 min)
+
+- 13:45-15:00 - {doc}`documentation`
+
+
+### Tuesday
+
+- 09:00-10:00 - {doc}`collaboration` (1/2)
+ - {doc}`collaboration/concepts` (15 min)
+ - {doc}`collaboration/same-repository` (45 min)
+
+- 10:15-11:30 - {doc}`collaboration` (2/2)
+ - {doc}`collaboration/code-review` (35 min)
+ - {doc}`collaboration/forking-workflow` (35 min)
+
+- 11:30-12:15 - Lunch break
+
+- 12:15-12:45 - {doc}`dependencies`
+
+- 12:45-13:30 - Working with Notebooks
+ - Notebooks and version control
+ - {doc}`notebooks/tooling`
+ - {doc}`notebooks/sharing`
+
+- 13:30-14:15 - Other useful tools for Python development
+ - {doc}`good-practices`
+ - {doc}`profiling`
+
+- 14:15-15:00 - Debriefing and Q&A
+ - Participants work on their projects
+ - Together we study actual codes that participants wrote or work on
+ - Constructively we discuss possible improvements
+ - Give individual feedback on code projects
+
+
+### Wednesday
+
+- 09:00-10:00 - {doc}`testing`
+
+- 10:15-11:30 - Modular code development
+ - {doc}`refactoring-concepts`
+ - How to parallelize independent tasks using workflows
+
+- 11:30-12:15 - Lunch break
+
+- 12:15-14:00 - How to release and publish your code
+ - {doc}`software-licensing` (30 min)
+ - {doc}`publishing` (15 min)
+ - {doc}`packaging` (45 min)
+
+- 14:15-15:00 - Debriefing and Q&A
+ - Participants work on their projects
+ - Together we study actual codes that participants wrote or work on
+ - Constructively we discuss possible improvements
+ - Give individual feedback on code projects
+
+
+### Thursday
+
+- 09:00-15:00
+ - Moving from laptop to high-performance computing (demo/type-along)
+ - Introduction to the exam
+
+
+:::{toctree}
+:maxdepth: 1
+:caption: Software environment
+:hidden:
+
+installation.md
+:::
+
+
+:::{toctree}
+:maxdepth: 1
+:caption: Episodes
+:hidden:
+
+example
+version-control
+documentation
+collaboration
+dependencies
+testing
+notebooks/tooling
+notebooks/sharing
+refactoring-concepts
+good-practices
+profiling
+software-licensing
+publishing
+packaging
+:::
+
+
+:::{toctree}
+:maxdepth: 1
+:caption: Reference
+:hidden:
+
+PDF version
+credit
+All lessons
+CodeRefinery
+Reusing
+:::
diff --git a/branch/main/_sources/installation.md.txt b/branch/main/_sources/installation.md.txt
new file mode 100644
index 0000000..d53ea68
--- /dev/null
+++ b/branch/main/_sources/installation.md.txt
@@ -0,0 +1,263 @@
+# Software install instructions
+
+[this page is adapted from ]
+
+
+## Choosing an installation method
+
+For this course we will install an **isolated environment**
+with following dependencies:
+:::::{tabs}
+ ::::{tab} environment.yml
+ :::{literalinclude} ../software/environment.yml
+ :language: yaml
+ :::
+ ::::
+
+ ::::{tab} requirements.txt
+ :::{literalinclude} ../software/requirements.txt
+ :::
+ ::::
+:::::
+
+:::{admonition} If you have a tool/method that works for you, keep using it
+If you are used to installing packages in Python and know what to do with the
+above files, please follow your own preferred installation
+method.
+:::
+
+**If you are new to Python or unsure** how to create isolated environments in
+Python from files above, please follow the
+instructions below.
+
+:::{discussion} There are many choices and we try to suggest a good compromise
+There are very many ways to install Python and packages with pros and cons and
+in addition there are several operating systems with their own quirks. This
+can be a huge challenge for beginners to navigate. It can also difficult for
+instructors to give recommendations for something which will work everywhere
+and which everybody will like.
+
+Below we will recommend **Miniforge** since it is free, open source, general,
+available on all operating systems, and provides a good basis for reproducible
+environments. However, it does not provide a graphical user interface during
+installation. This means that every time we want to start a JupyterLab session,
+we will have to go through the command line.
+:::
+
+:::{admonition} Python, conda, anaconda, miniforge, etc?
+:class: dropdown
+
+Unfortunately there are many options and a lot of jargon.
+Here is a crash course:
+
+- **Python** is a programming language very commonly used in
+ science, it's the topic of this course.
+- **Conda** is a package manager: it allows distributing and
+ installing packages, and is designed for complex scientific
+ code.
+- **Mamba** is a re-implementation of Conda to be much faster with
+ resolving dependencies and installing things.
+- An **environment** is a self-contained collections of packages
+ which can be installed separately from others. They are used so
+ each project can install what it needs without affecting others.
+- **Anaconda** is a commercial distribution of Python+Conda+many
+ packages that all work together. It used to be freely usable for
+ research, but since ~2023-2024 it's more limited. Thus, we don't
+ recommend it (even though it has a nice graphical user interface).
+- **conda-forge** is another channel of distributing packages that
+ is maintained by the community, and thus can be used by anyone.
+ (Anaconda's parent company also hosts conda-forge packages)
+- **Miniforge** is a distribution of conda pre-configured for
+ conda-forge. It operates via the command line.
+- **Miniconda** is a distribution of conda pre-configured to use
+ the Anaconda channels.
+
+We will gain a better background and overview in the section
+{doc}`dependencies`.
+:::
+
+
+## Installing Python via Miniforge
+
+Follow the [instructions on the miniforge web page](https://github.com/conda-forge/miniforge). This installs
+the base, and from here other packages can be installed.
+
+Unsure what to download and what to do with it?
+::::{tabs}
+ :::{group-tab} Windows
+ You want to download and run `Miniforge3-Windows-x86_64.exe`.
+ :::
+
+ :::{group-tab} MacOS
+ You probably want to download the `Miniforge3-MacOSX-arm64.sh` file (unless
+ you don't have an Arm processor) and then run it in the terminal with:
+ ```console
+ $ bash Miniforge3-MacOSX-arm64.sh
+ ```
+ :::
+
+ :::{group-tab} Linux
+ You probably want to download the `Miniforge3-Linux-x86_64.sh` file (unless
+ you don't have an x86_64 processor) and then run it in the terminal with:
+ ```console
+ $ bash Miniforge3-Linux-x86_64.sh
+ ```
+ :::
+::::
+
+
+
+## Installing and activating the software environment
+
+First we will start Python in a way that activates conda/mamba. Then we will
+install the software environment from [this environment.yml
+file](https://raw.githubusercontent.com/coderefinery/reproducible-python-ml/main/software/environment.yml).
+
+An **environment** is a self-contained set of extra libraries - different
+projects can use different environments to not interfere with each other. This
+environment will have all of the software needed for this particular course.
+
+We will call the environment `course`.
+
+::::{tabs}
+ :::{group-tab} Windows
+ Use the "Miniforge Prompt" to start Miniforge. This
+ will set up everything so that ``conda`` and ``mamba`` are
+ available.
+ Then type
+ (without the `$`):
+ ```console
+ $ mamba env create -n course -f https://raw.githubusercontent.com/coderefinery/reproducible-python-ml/main/software/environment.yml
+ ```
+ :::
+
+ :::{group-tab} Linux / MacOS
+ Each time you start a new command line terminal,
+ you can activate Miniforge by running
+ (without the `$`):
+ ```console
+ $ source ~/miniforge3/bin/activate
+ ```
+
+ This is needed so that
+ Miniforge is usable wherever you need, but doesn't affect any
+ other software on your computer (this is not needed if you
+ choose "Do you wish to update your shell profile to
+ automatically initialize conda?", but then it will always be
+ active).
+
+ In the second step, we will install the software environment:
+ ```console
+ $ mamba env create -n course -f https://raw.githubusercontent.com/coderefinery/reproducible-python-ml/main/software/environment.yml
+ ```
+ :::
+::::
+
+
+## Starting JupyterLab
+
+Every time we want to start a JupyterLab session,
+we will have to go through the command line and first
+activate the `course` environment.
+
+::::{tabs}
+ :::{group-tab} Windows
+ Start the Miniforge Prompt. Then type
+ (without the `$`):
+ ```console
+ $ conda activate course
+ $ jupyter-lab
+ ```
+ :::
+
+ :::{group-tab} Linux / MacOS
+ Start the terminal and in the terminal, type
+ (without the `$`):
+ ```console
+ $ source ~/miniforge3/bin/activate
+ $ conda activate course
+ $ jupyter-lab
+ ```
+ :::
+::::
+
+
+## Removing the software environment
+
+::::{tabs}
+ :::{group-tab} Windows
+ In the Miniforge Prompt, type
+ (without the `$`):
+ ```console
+ $ conda env list
+ $ conda env remove --name course
+ $ conda env list
+ ```
+ :::
+
+ :::{group-tab} Linux / MacOS
+ In the terminal, type
+ (without the `$`):
+ ```console
+ $ source ~/miniforge3/bin/activate
+ $ conda env list
+ $ conda env remove --name course
+ $ conda env list
+ ```
+ :::
+::::
+
+
+## How to verify your installation
+
+**Start JupyterLab** (as described above). It will hopefully open up your browser
+and look like this:
+
+:::{figure} img/installation/testing-jupyter1.png
+:alt: JupyterLab opened in the browser
+:width: 100%
+
+JupyterLab opened in the browser. Click on the Python 3 tile.
+:::
+
+Once you clicked the Python 3 tile it should look like this:
+:::{figure} img/installation/testing-jupyter2.png
+:alt: Python 3 notebook started
+:width: 100%
+
+Python 3 notebook started.
+:::
+
+Into that blue "cell" please type the following:
+```python
+import altair
+import pandas
+
+print("all good - ready for the course")
+```
+
+:::{figure} img/installation/testing-jupyter3.png
+:alt: Screenshot where we test to import required libraries.
+:width: 100%
+
+Please copy these lines and click on the "play"/"run" icon.
+:::
+
+This is how it should look:
+:::{figure} img/installation/testing-jupyter4.png
+:alt: Screenshot after successful import.
+:width: 100%
+
+Screenshot after successful import.
+:::
+
+If this worked, you are all set and can close JupyterLab (no need to save these
+changes).
+
+This is how it **should not** look:
+:::{figure} img/installation/testing-jupyter5.png
+:alt: Required packages could not be found.
+:width: 100%
+
+Error: required packages could not be found.
+:::
diff --git a/branch/main/_sources/notebooks/sharing.md.txt b/branch/main/_sources/notebooks/sharing.md.txt
new file mode 100644
index 0000000..963c78c
--- /dev/null
+++ b/branch/main/_sources/notebooks/sharing.md.txt
@@ -0,0 +1,83 @@
+# Sharing notebooks
+
+:::{objectives}
+- Know about good practices for notebooks to make them reusable
+- Have a recipe to share a dynamic and reproducible visualization pipeline
+:::
+
+[this lesson is adapted after ]
+
+
+## Document dependencies
+
+- If you import libraries into your notebook, note down their versions.
+ Document the dependencies as discussed in section {doc}`../dependencies`.
+
+- Place either `environment.yml` or `requirements.txt` in the same folder as
+ the notebook(s).
+
+- If you publish the notebook as part of a publication, it is probably a good
+ idea to **pin the versions** of the libraries you use.
+
+- This is not only useful for people who will try to rerun this in future, it
+ is also understood by some tools (e.g. [Binder](https://mybinder.org/)) which we
+ will see later.
+
+
+## Different ways to share a notebook
+
+We need to learn how to share notebooks. At the minimum we need
+to share them with our future selves (backup and reproducibility).
+
+- You can enter a URL, GitHub repo or username, or GIST ID in [nbviewer](https://nbviewer.jupyter.org/) and view a rendered Jupyter notebook
+- Read the Docs can render Jupyter Notebooks via the [nbsphinx package](https://nbsphinx.readthedocs.io/)
+- [Binder](https://mybinder.org/) creates live notebooks based on a GitHub repository
+- [EGI Notebooks](https://notebooks.egi.eu) (see also )
+- [JupyterLab](https://github.com/jupyterlab/jupyterlab) supports sharing and collaborative editing of notebooks via Google Drive. Recently
+ it also added support for [Shared editing with collaborative notebook model](https://github.com/jupyterlab/jupyterlab/pull/10118).
+- [JupyterLite](https://jupyterlite.readthedocs.io/en/latest/) creates a Jupyterlab environment in the browser and can be hosted as a GitHub page.
+- [Notedown](https://github.com/aaren/notedown), [Jupinx](https://github.com/QuantEcon/sphinxcontrib-jupyter) and [DocOnce](https://github.com/hplgit/doconce) can take Markdown or Sphinx files and generate Jupyter Notebooks
+- [Voilà](https://voila.readthedocs.io/en/stable/) allows you to convert a Jupyter Notebook into an interactive dashboard
+- The `jupyter nbconvert` tool can convert a (`.ipynb`) notebook file to:
+ - python code (`.py` file)
+ - an HTML file
+ - a LaTeX file
+ - a PDF file
+ - a slide-show in the browser
+
+
+The following platforms can be used free of charge but have **paid subscriptions** for
+faster access to cloud resources:
+- [CoCalc](https://cocalc.com/) (formerly SageMathCloud) allows collaborative editing of notebooks in the cloud
+- [Google Colab](https://colab.research.google.com/) lets you work on notebooks in the cloud, and you can [read and write to notebook files on Drive](https://colab.research.google.com/notebooks/io.ipynb)
+- [Microsoft Azure Notebooks](https://notebooks.azure.com/) also offers free notebooks in the cloud
+- [Deepnote](https://deepnote.com/) allows real-time collaboration
+
+
+## Sharing dynamic notebooks using [Binder](https://mybinder.org/)
+
+::::{challenge} Exercise/demo: Making your notebooks reproducible by anyone (15 min)
+Instructor demonstrates this:
+- First we look at the statically rendered version of the [example
+ notebook](https://github.com/workshop-material/classification-task/blob/main/example.ipynb)
+ on GitHub and also [nbviewer](https://nbviewer.jupyter.org/).
+- Visit [https://mybinder.org](https://mybinder.org):
+ :::{figure} binder.jpg
+ :alt: Screenshot from mybinder.org user interface
+ :::
+- Check that your notebook repository now has a "launch binder"
+ badge in your `README.md` file on GitHub.
+- Try clicking the button and see how your repository is launched
+ on Binder (can take a minute or two). Your notebooks can now be
+ explored and executed in the cloud.
+- Enjoy being fully reproducible!
+::::
+
+
+## How to get a digital object identifier (DOI)
+
+- [Zenodo](https://zenodo.org/) is a great service to get a
+ [DOI](https://en.wikipedia.org/wiki/Digital_object_identifier) for a notebook
+ (but **first practice** with the [Zenodo sandbox](https://sandbox.zenodo.org/)).
+- [Binder](https://mybinder.org/) can also run notebooks from Zenodo.
+- In the supporting information of your paper you can refer to its DOI.
diff --git a/branch/main/_sources/notebooks/tooling.md.txt b/branch/main/_sources/notebooks/tooling.md.txt
new file mode 100644
index 0000000..405d29f
--- /dev/null
+++ b/branch/main/_sources/notebooks/tooling.md.txt
@@ -0,0 +1,25 @@
+# Other useful tooling for notebooks
+
+
+## Code formatting
+
+
+
+
+:::{figure} packages.jpg
+:alt: Screenshot of three additional packages in environment.yml
+
+We need three additional packages to format code in JupyterLab: `jupyterlab-code-formatter`, `black`, and `isort`.
+:::
+
+
+:::{figure} button.jpg
+:alt: Screenshot of the button to format code in JupyterLab
+
+This button will format the code in all cells of the notebook.
+:::
+
+
+:::{instructor-note}
+We test it out together on an example notebook.
+:::
diff --git a/branch/main/_sources/packaging.md.txt b/branch/main/_sources/packaging.md.txt
new file mode 100644
index 0000000..dbefbfc
--- /dev/null
+++ b/branch/main/_sources/packaging.md.txt
@@ -0,0 +1,180 @@
+# Creating a Python package and deploying it to PyPI
+
+:::{objectives}
+In this episode we will create a pip-installable Python package and learn how
+to deploy it to PyPI. As example, we can use one of the Python scripts from our
+example repository.
+:::
+
+
+## Creating a Python package with the help of [Flit](https://flit.pypa.io/)
+
+There are unfortunately many ways to package a Python project:
+- `setuptools` is the most common way to package a Python project. It is very
+ powerful and flexible, but also can get complex.
+- `flit` is a simpler alternative to `setuptools`. It is less versatile, but
+ also easier to use.
+- `poetry` is a modern packaging tool which is more versatile than `flit` and
+ also easier to use than `setuptools`.
+- `twine` is another tool to upload packages to PyPI.
+- ...
+
+:::{admonition} This will be a demo
+- We will try to package the code together on the big screen.
+- We will share the result on GitHub so that you can retrace the steps.
+- In this demo, we will use Flit to package the code. From [Why use Flit?](https://flit.pypa.io/en/latest/rationale.html):
+ > Make the easy things easy and the hard things possible is an old motto from
+ > the Perl community. Flit is entirely focused on the easy things part of that,
+ > and leaves the hard things up to other tools.
+:::
+
+
+## Step 1: Initialize the package metadata and try a local install
+
+1. Our starting point is that we have a Python script called `example.py`
+ which we want to package.
+
+2. Now we follow the [flit quick-start usage](https://flit.pypa.io/en/stable/#usage) and add a docstring to the script and a `__version__`.
+
+3. We then run `flit init` to create a `pyproject.toml` file and answer few questions. I obtained:
+ ```{code-block} toml
+ ---
+ emphasize-lines: 6, 13
+ ---
+ [build-system]
+ requires = ["flit_core >=3.2,<4"]
+ build-backend = "flit_core.buildapi"
+
+ [project]
+ name = "example"
+ authors = [{name = "Firstname Lastname", email = "first.last@example.org"}]
+ license = {file = "LICENSE"}
+ classifiers = ["License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)"]
+ dynamic = ["version", "description"]
+
+ [project.urls]
+ Home = "https://example.org"
+ ```
+ To have a more concrete example, if we package the `generate_data.py` script
+ from the [example
+ repository](https://github.com/workshop-material/classification-task), then
+ replace the name `example` with `generate_data`.
+
+4. We now add dependencies and also an entry point for the script:
+ ```{code-block} toml
+ ---
+ emphasize-lines: 11-15, 20-21
+ ---
+ [build-system]
+ requires = ["flit_core >=3.2,<4"]
+ build-backend = "flit_core.buildapi"
+
+ [project]
+ name = "example"
+ authors = [{name = "Firstname Lastname", email = "first.last@example.org"}]
+ license = {file = "LICENSE"}
+ classifiers = ["License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)"]
+ dynamic = ["version", "description"]
+ dependencies = [
+ "click",
+ "numpy",
+ "pandas",
+ ]
+
+ [project.urls]
+ Home = "https://example.org"
+
+ [project.scripts]
+ example = "example:main"
+ ```
+
+5. Before moving on, try a local install:
+ ```console
+ $ flit install --symlink
+ ```
+
+:::{discussion} What if you have more than just one script?
+- Create a directory with the name of the package.
+- Put the scripts in the directory.
+- Add a `__init__.py` file to the directory which contains the module
+ docstring and the version and re-exports the functions from the scripts.
+:::
+
+
+## Step 2: Testing an install from GitHub
+
+If a local install worked, push the `pyproject.toml` to GitHub and try to install the package from GitHub.
+
+In a `requirements.txt` file, you can specify the GitHub repository and the
+branch (adapt the names):
+```
+git+https://github.com/ORGANIZATION/REPOSITORY.git@main
+```
+
+A corresponding `envionment.yml` file for conda would look like this (adapt the
+names):
+```yaml
+name: experiment
+channels:
+ - conda-forge
+dependencies:
+ - python <= 3.12
+ - pip
+ - pip:
+ - git+https://github.com/ORGANIZATION/REPOSITORY.git@main
+```
+
+Does it install and run? If yes, move on to the next step (test-PyPI and later
+PyPI).
+
+
+## Step 3: Deploy the package to test-PyPI using GitHub Actions
+
+Our final step is to create a GitHub Actions workflow which will run when we
+create a new release.
+
+:::{danger}
+I recommend to first practice this on the test-PyPI before deploying to the
+real PyPI.
+:::
+
+Here is the workflow (`.github/workflows/package.yml`):
+```yaml
+name: Package
+
+on:
+ release:
+ types: [created]
+
+jobs:
+ build:
+ permissions: write-all
+ runs-on: ubuntu-24.04
+
+ steps:
+ - name: Switch branch
+ uses: actions/checkout@v4
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.12"
+ - name: Install Flit
+ run: |
+ pip install flit
+ - name: Flit publish
+ run:
+ flit publish
+ env:
+ FLIT_USERNAME: __token__
+ FLIT_PASSWORD: ${{ secrets.PYPI_TOKEN }}
+ # uncomment the following line if you are using test.pypi.org:
+# FLIT_INDEX_URL: https://test.pypi.org/legacy/
+ # this is how you can test the installation from test.pypi.org:
+# pip install --index-url https://test.pypi.org/simple/ package_name
+```
+
+About the `FLIT_PASSWORD: ${{ secrets.PYPI_TOKEN }}`:
+- You obtain the token from PyPI by going to your account settings and
+ creating a new token.
+- You add the token to your GitHub repository as a secret (here with the name
+ `PYPI_TOKEN`).
diff --git a/branch/main/_sources/profiling.md.txt b/branch/main/_sources/profiling.md.txt
new file mode 100644
index 0000000..18db5eb
--- /dev/null
+++ b/branch/main/_sources/profiling.md.txt
@@ -0,0 +1,214 @@
+# Profiling
+
+:::{objectives}
+- Understand when improving code performance is worth the time and effort.
+- Knowing how to find performance bottlenecks in Python code.
+- Try [Scalene](https://github.com/plasma-umass/scalene) as one of many tools
+ to profile Python code.
+:::
+
+[This page is adapted after ]
+
+
+## Should we even optimize the code?
+
+Classic quote to keep in mind: "Premature optimization is the root of all evil." [Donald Knuth]
+
+:::{discussion}
+It is important to ask ourselves whether it is worth it.
+- Is it worth spending e.g. 2 days to make a program run 20% faster?
+- Is it worth optimizing the code so that it spends 90% less memory?
+
+Depends. What does it depend on?
+:::
+
+
+## Measure instead of guessing
+
+Before doing code surgery to optimize the run time or lower the memory usage,
+we should **measure** where the bottlenecks are. This is called **profiling**.
+
+Analogy: Medical doctors don't start surgery based on guessing. They first measure
+(X-ray, MRI, ...) to know precisely where the problem is.
+
+Not only programming beginners can otherwise guess wrong, but also experienced
+programmers can be surprised by the results of profiling.
+
+
+## One of the simplest tools is to insert timers
+
+Below we will list some tools that can be used to profile Python code.
+But even without these tools you can find **time-consuming parts** of your code
+by inserting timers:
+
+```{code-block} python
+---
+emphasize-lines: 1,8,10
+---
+import time
+
+
+# ...
+# code before the function
+
+
+start = time.time()
+result = some_function()
+print(f"some_function took {time.time() - start} seconds")
+
+
+# code after the function
+# ...
+```
+
+
+## Many tools exist
+
+The list below here is probably not complete, but it gives an overview of the
+different tools available for profiling Python code.
+
+CPU profilers:
+- [cProfile and profile](https://docs.python.org/3/library/profile.html)
+- [line_profiler](https://kernprof.readthedocs.io/)
+- [py-spy](https://github.com/benfred/py-spy)
+- [Yappi](https://github.com/sumerc/yappi)
+- [pyinstrument](https://pyinstrument.readthedocs.io/)
+- [Perfetto](https://perfetto.dev/docs/analysis/trace-processor-python)
+
+Memory profilers:
+- [memory_profiler](https://pypi.org/project/memory-profiler/) (not actively maintained)
+- [Pympler](https://pympler.readthedocs.io/)
+- [tracemalloc](https://docs.python.org/3/library/tracemalloc.html)
+- [guppy/heapy](https://github.com/zhuyifei1999/guppy3/)
+
+Both CPU and memory:
+- [Scalene](https://github.com/plasma-umass/scalene)
+
+In the exercise below, we will use Scalene to profile a Python program. Scalene
+is a sampling profiler that can profile CPU, memory, and GPU usage of Python.
+
+
+## Tracing profilers vs. sampling profilers
+
+**Tracing profilers** record every function call and event in the program,
+logging the exact sequence and duration of events.
+- **Pros:**
+ - Provides detailed information on the program's execution.
+ - Deterministic: Captures exact call sequences and timings.
+- **Cons:**
+ - Higher overhead, slowing down the program.
+ - Can generate larger amount of data.
+
+**Sampling profilers** periodically samples the program's state (where it is
+and how much memory is used), providing a statistical view of where time is
+spent.
+- **Pros:**
+ - Lower overhead, as it doesn't track every event.
+ - Scales better with larger programs.
+- **Cons:**
+ - Less precise, potentially missing infrequent or short calls.
+ - Provides an approximation rather than exact timing.
+
+:::{discussion} Analogy: Imagine we want to optimize the London Underground (subway) system
+We wish to detect bottlenecks in the system to improve the service and for this we have
+asked few passengers to help us by tracking their journey.
+- **Tracing**: We follow every train and passenger, recording every stop
+ and delay. When passengers enter and exit the train, we record the exact time
+ and location.
+- **Sampling**: Every 5 minutes the phone notifies the passenger to note
+ down their current location. We then use this information to estimate
+ the most crowded stations and trains.
+:::
+
+
+## Choosing the right system size
+
+Sometimes we can configure the system size (for instance the time step in a simulation
+or the number of time steps or the matrix dimensions) to make the program finish sooner.
+
+For profiling, we should choose a system size that is **representative of the real-world**
+use case. If we profile a program with a small input size, we might not see the same
+bottlenecks as when running the program with a larger input size.
+
+Often, when we scale up the system size, or scale the number of processors, new bottlenecks
+might appear which we didn't see before. This brings us back to: "measure instead of guessing".
+
+
+## Exercises
+
+::::{exercise} Exercise: Practicing profiling
+In this exercise we will use the Scalene profiler to find out where most of the time is spent
+and most of the memory is used in a given code example.
+
+Please try to go through the exercise in the following steps:
+1. Make sure `scalene` is installed in your environment (if you have followed
+ this course from the start and installed the recommended software
+ environment, then it is).
+1. Download Leo Tolstoy's "War and Peace" from the following link (the text is
+ provided by [Project Gutenberg](https://www.gutenberg.org/)):
+
+ (right-click and "save as" to download the file and **save it as "book.txt"**).
+1. **Before** you run the profiler, try to predict in which function the code
+ (the example code is below)
+ will spend most of the time and in which function it will use most of the
+ memory.
+1. Save the example code as `example.py` and
+ run the `scalene` profiler on the following code example and browse the
+ generated HTML report to find out where most of the time is spent and where
+ most of the memory is used:
+ ```console
+ $ scalene example.py
+ ```
+ Alternatively you can do this (and then open the generated file in a browser):
+ ```console
+ $ scalene example.py --html > profile.html
+ ```
+ You can find an example of the generated HTML report in the solution below.
+1. Does the result match your prediction? Can you explain the results?
+
+Example code (`example.py`):
+:::{literalinclude} profiling/exercise.py
+:::
+
+:::{solution}
+ ```{figure} profiling/exercise.png
+ :alt: Result of the profiling run for the above code example.
+ :width: 100%
+
+ Result of the profiling run for the above code example. You can click on the image to make it larger.
+ ```
+
+ Results:
+ - Most time is spent in the `count_unique_words2` function.
+ - Most memory is used in the `count_unique_words1` function.
+
+ Explanation:
+ - The `count_unique_words2` function is the slowest because it **uses a list**
+ to store unique words and checks if a word is already in the list before
+ adding it.
+ Checking whether a list contains an element might require traversing the
+ whole list, which is an O(n) operation. As the list grows in size,
+ the lookup time increases with the size of the list.
+ - The `count_unique_words1` and `count_unique_words3` functions are faster
+ because they **use a set** to store unique words.
+ Checking whether a set contains an element is an O(1) operation.
+ - The `count_unique_words1` function uses the most memory because it **creates
+ a list of all words** in the text file and then **creates a set** from that
+ list.
+ - The `count_unique_words3` function uses less memory because it traverses
+ the text file line by line instead of reading the whole file into memory.
+
+ What we can learn from this exercise:
+ - When processing large files, it can be good to read them line by line
+ or in batches
+ instead of reading the whole file into memory.
+ - It is good to get an overview over standard data structures and their
+ advantages and disadvantages (e.g. adding an element to a list is fast but checking whether
+ it already contains the element can be slow).
+ :::
+::::
+
+
+## Additional resources
+
+- [Python performance workshop (by ENCCS)](https://enccs.github.io/python-perf/profile/)
diff --git a/branch/main/_sources/publishing.md.txt b/branch/main/_sources/publishing.md.txt
new file mode 100644
index 0000000..89d3f94
--- /dev/null
+++ b/branch/main/_sources/publishing.md.txt
@@ -0,0 +1,132 @@
+# How to publish your code
+
+:::{objectives}
+- Make our code citable and persistent.
+- Make our Notebook reusable and persistent.
+:::
+
+
+## Is putting software on GitHub/GitLab/... publishing?
+
+```{figure} img/turing-way/8-fair-principles.jpg
+:alt: FAIR principles
+:width: 70%
+
+FAIR principles. (c) [Scriberia](http://www.scriberia.co.uk) for [The Turing Way](https://the-turing-way.netlify.com), CC-BY.
+```
+
+Is it enough to make the code public for the code to remain **findable and accessible**?
+- No. Because nothing prevents me from deleting my GitHub repository or
+ rewriting the Git history and we have no guarantee that GitHub will still be around in 10 years.
+- **Make your code citable and persistent**:
+ Get a persistent identifier (PID) such as DOI in addition to sharing the
+ code publicly, by using services like [Zenodo](https://zenodo.org) or
+ similar services.
+
+
+## How to make your software citable
+
+```{discussion} Discussion: Explain how you currently cite software
+- Do you cite software that you use? How?
+- If I wanted to cite your code/scripts, what would I need to do?
+```
+
+**Checklist for making a release of your software citable**:
+
+- Assigned an appropriate license
+- Described the software using an appropriate metadata format
+- Clear version number
+- Authors credited
+- Procured a persistent identifier
+- Added a recommended citation to the software documentation
+
+This checklist is adapted from: N. P. Chue Hong, A. Allen, A. Gonzalez-Beltran,
+et al., Software Citation Checklist for Developers (Version 0.9.0). Zenodo.
+2019b. ([DOI](https://doi.org/10.5281/zenodo.3482769))
+
+**Our practical recommendations**:
+- Add a file called [CITATION.cff](https://citation-file-format.github.io/) ([example](https://github.com/bast/runtest/blob/main/CITATION.cff)).
+- Get a [digital object identifier
+ (DOI)](https://en.wikipedia.org/wiki/Digital_object_identifier) for your code
+ on [Zenodo](https://zenodo.org/) ([example](https://zenodo.org/record/8003695)).
+- Make it as easy as possible: clearly say what you want cited.
+
+This is an example of a simple `CITATION.cff` file:
+```yaml
+cff-version: 1.2.0
+message: "If you use this software, please cite it as below."
+authors:
+ - family-names: Doe
+ given-names: Jane
+ orcid: https://orcid.org/1234-5678-9101-1121
+title: "My Research Software"
+version: 2.0.4
+doi: 10.5281/zenodo.1234
+date-released: 2021-08-11
+```
+
+More about `CITATION.cff` files:
+- [GitHub now supports CITATION.cff files](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-citation-files)
+- [Web form to create, edit, and validate CITATION.cff files](https://citation-file-format.github.io/cff-initializer-javascript/)
+- [Video: "How to create a CITATION.cff using cffinit"](https://www.youtube.com/watch?v=zcgLIT5Qd4M)
+
+
+## Papers with focus on scientific software
+
+Where can I publish papers which are primarily focused on my scientific
+software? Great list/summary is provided in this blog post: ["In which
+journals should I publish my software?" (Neil P. Chue
+Hong)](https://www.software.ac.uk/top-tip/which-journals-should-i-publish-my-software)
+
+
+## How to cite software
+
+```{admonition} Great resources
+- A. M. Smith, D. S. Katz, K. E. Niemeyer, and FORCE11 Software Citation
+ Working Group, "Software citation principles," PeerJ Comput. Sci., vol. 2,
+ no. e86, 2016 ([DOI](https://doi.org/10.7717/peerj-cs.86))
+- D. S. Katz, N. P. Chue Hong, T. Clark, et al., Recognizing the value of
+ software: a software citation guide [version 2; peer review: 2 approved].
+ F1000Research 2021, 9:1257 ([DOI](https://doi.org/10.12688/f1000research.26932.2))
+- N. P. Chue Hong, A. Allen, A. Gonzalez-Beltran, et al., Software Citation
+ Checklist for Authors (Version 0.9.0). Zenodo. 2019a. ([DOI](https://doi.org/10.5281/zenodo.3479199))
+- N. P. Chue Hong, A. Allen, A. Gonzalez-Beltran, et al., Software Citation
+ Checklist for Developers (Version 0.9.0). Zenodo. 2019b. ([DOI](https://doi.org/10.5281/zenodo.3482769))
+```
+
+Recommended format for software citation is to ensure the following information
+is provided as part of the reference (from [Katz, Chue Hong, Clark,
+2021](https://doi.org/10.12688/f1000research.26932.2) which also contains
+software citation examples):
+- Creator
+- Title
+- Publication venue
+- Date
+- Identifier
+- Version
+- Type
+
+
+
+## Exercise/demo
+
+:::{exercise}
+- We will add a `CITATION.cff` file to our example repository.
+- We will get a DOI using the [Zenodo sandbox](https://sandbox.zenodo.org):
+ - We will log into the [Zenodo sandbox](https://sandbox.zenodo.org) using
+ GitHub.
+ - We will follow [these steps](https://docs.github.com/en/repositories/archiving-a-github-repository/referencing-and-citing-content)
+ and finally create a GitHub release and get a DOI.
+- We can try to create an example repository with a Jupyter Notebook and run it through [Binder](https://mybinder.org/)
+ to make it persistent and citable.
+:::
+
+:::{discussion}
+- Why did we use the Zenodo sandbox and not the "real" Zenodo for our exercise?
+:::
+
+
+## More resources
+
+- [Social coding lesson material](https://coderefinery.github.io/social-coding/software-citation/)
+- [Sharing Jupiter Notebooks](https://coderefinery.github.io/jupyter/sharing/)
diff --git a/branch/main/_sources/refactoring-concepts.md.txt b/branch/main/_sources/refactoring-concepts.md.txt
new file mode 100644
index 0000000..554d03b
--- /dev/null
+++ b/branch/main/_sources/refactoring-concepts.md.txt
@@ -0,0 +1,123 @@
+# Concepts in refactoring and modular code design
+
+
+## Starting questions for the collaborative document
+
+1. What does "modular code development" mean for you?
+2. What best practices can you recommend to arrive at well structured,
+ modular code in your favourite programming language?
+3. What do you know now about programming that you wish somebody told you
+ earlier?
+4. Do you design a new code project on paper before coding? Discuss pros and
+ cons.
+5. Do you build your code top-down (starting from the big picture) or
+ bottom-up (starting from components)? Discuss pros and cons.
+6. Would you prefer your code to be 2x slower if it was easier to read and
+ understand?
+
+
+## Pure functions
+
+- Pure functions have no notion of state: They take input values and return
+ values
+- **Given the same input, a pure function always returns the same value**
+- Function calls can be optimized away
+- Pure function == data
+
+a) pure: no side effects
+```python
+def fahrenheit_to_celsius(temp_f):
+ temp_c = (temp_f - 32.0) * (5.0/9.0)
+ return temp_c
+
+temp_c = fahrenheit_to_celsius(temp_f=100.0)
+print(temp_c)
+```
+
+b) stateful: side effects
+```python
+f_to_c_offset = 32.0
+f_to_c_factor = 0.555555555
+temp_c = 0.0
+
+def fahrenheit_to_celsius_bad(temp_f):
+ global temp_c
+ temp_c = (temp_f - f_to_c_offset) * f_to_c_factor
+
+fahrenheit_to_celsius_bad(temp_f=100.0)
+print(temp_c)
+```
+
+Pure functions are easier to:
+- Test
+- Understand
+- Reuse
+- Parallelize
+- Simplify
+- Optimize
+- Compose
+
+
+Mathematical functions are pure:
+```{math}
+f(x, y) = x - x^2 + x^3 + y^2 + xy
+```
+
+```{math}
+(f \circ g)(x) = f(g(x))
+```
+
+Unix shell commands are stateless:
+```shell
+$ cat somefile | grep somestring | sort | uniq | ...
+```
+
+
+## But I/O and network and disk and databases are not pure!
+
+- I/O is impure
+- Keep I/O on the "outside" of your code
+- Keep the "inside" of your code pure/stateless
+
+:::{figure} img/good-vs-bad.svg
+:alt: Image comparing code that is mostly impure to code where the impure parts are on the outside
+:::
+
+
+## From classes to functions
+
+Object-oriented programming and functional programming **both have their place
+and value**.
+
+Here is an example of expressing the same thing in Python in 4 different ways.
+Which one do you prefer?
+
+1. As a **class**:
+ ```{literalinclude} refactoring/using-class.py
+ :language: python
+ ```
+
+2. As a **dataclass**:
+ ```{literalinclude} refactoring/using-dataclass.py
+ :language: python
+ ```
+
+3. As a **named tuple**:
+ ```{literalinclude} refactoring/using-namedtuple.py
+ :language: python
+ ```
+
+4. As a **dict**:
+ ```{literalinclude} refactoring/using-dict.py
+ :language: python
+ ```
+
+
+## How to design your code before writing it
+
+- Document-driven development can be a nice approach:
+ - Write the documentation/tutorial first
+ - Write the code to make the documentation true
+ - Refactor the code to make it clean and maintainable
+- But also it's almost impossible to design everything correctly from the
+ start -> make it easy to change -> keep it simple
diff --git a/branch/main/_sources/software-licensing.md.txt b/branch/main/_sources/software-licensing.md.txt
new file mode 100644
index 0000000..63fd098
--- /dev/null
+++ b/branch/main/_sources/software-licensing.md.txt
@@ -0,0 +1,151 @@
+# Choosing a software license
+
+:::{objectives}
+- Knowing about what derivative work is and whether we can share it.
+- Get familiar with terminology around licensing.
+- We will add a license to our example project.
+:::
+
+
+## Copyright and derivative work: Sampling/remixing
+
+:::{figure} img/ai/record-player.png
+:alt: Generated image of a monk operating a record player
+:width: 50%
+:::
+[Midjourney, CC-BY-NC 4.0]
+
+:::{figure} img/ai/turntable.png
+:alt: Generated image of a monk operating two record players
+:width: 50%
+:::
+[Midjourney, CC-BY-NC 4.0]
+
+- Copyright controls whether and how we can distribute
+ the original work or the **derivative work**.
+- In the **context of software** it is more about
+ being able to change and **distribute changes**.
+- Changing and distributing software is similar to changing and distributing
+ music
+- You can do almost anything if you don't distribute it
+
+**Often we don't have the choice**:
+- We are expected to publish software
+- Sharing can be good insurance against being locked out
+
+**Can we distribute our changes** with the research community or our future selves?
+
+
+## Why software licenses matter
+
+You find some great code that you want to reuse for your own publication.
+
+- This is good for the original author - you will cite them. Maybe other people who cite you will cite them.
+
+- You modify and remix the code.
+
+- Two years later ... ⌛
+
+- Time to publish: You realize **there is no license to the original work** 😱
+
+**Now we have a problem**:
+- 😬 "Best" case: You manage to publish the paper without the software/data.
+ Others cannot build on your software and data.
+- 😱 Worst case: You cannot publish it at all.
+ Journal requires that papers should come with data and software so that they are reproducible.
+
+
+## Taxonomy of software licenses
+
+:::{figure} img/license-models.png
+:alt: "European Union Public Licence (EUPL): guidelines July 2021"
+
+European Commission, Directorate-General for Informatics, Schmitz, P., European Union Public Licence (EUPL): guidelines July 2021, Publications Office, 2021,
+:::
+
+Comments:
+- Arrows represent compatibility (A -> B: B can reuse A)
+- Proprietary/custom: Derivative work typically not possible (no arrow goes from proprietary to open)
+- Permissive: Derivative work does not have to be shared
+- Copyleft/reciprocal: Derivative work must be made available under the same license terms
+- NC (non-commercial) and ND (non-derivative) exist for data licenses but not really for software licenses
+
+**Great resource for comparing software licenses**: [Joinup Licensing Assistant](https://joinup.ec.europa.eu/collection/eupl/solution/joinup-licensing-assistant/jla-find-and-compare-software-licenses)
+- Provides comments on licenses
+- Easy to compare licenses ([example](https://joinup.ec.europa.eu/licence/compare/BSD-3-Clause;Apache-2.0))
+- [Joinup Licensing Assistant - Compatibility Checker](https://joinup.ec.europa.eu/collection/eupl/solution/joinup-licensing-assistant/jla-compatibility-checker)
+- Not biased by some company agenda
+
+
+## Exercise/demo
+
+:::{exercise}
+- Let us choose a license for our example project.
+- We will add a LICENSE to the repository.
+:::
+
+:::{discussion}
+- What if my code uses libraries like `numpy`, `pandas`, `scipy`,
+ `altair`, etc. Do we need to look at their licenses? In other words,
+ **is our project derivative work** of something else?
+:::
+
+
+## More resources
+
+- Presentation slides "Practical software licensing" (R. Bast):
+- [Social coding lesson material](https://coderefinery.github.io/social-coding/)
+- [UiT research software licensing guide (draft)](https://research-software.uit.no/blog/2023-software-licensing-guide/)
+- [Research institution policies to support research software (compiled by the Research Software Alliance)](https://www.researchsoft.org/software-policies/)
+- More [reading material](https://coderefinery.github.io/social-coding/software-licensing/#great-resources)
+
+
+## More exercises
+
+:::{exercise} Exercise: What constitutes derivative work?
+
+Which of these are derivative works? Also reflect/discuss how this affects the
+choice of license.
+- A. Download some code from a website and add on to it
+- B. Download some code and use one of the functions in your code
+- C. Changing code you got from somewhere
+- D. Extending code you got from somewhere
+- E. Completely rewriting code you got from somewhere
+- F. Rewriting code to a different programming language
+- G. Linking to libraries (static or dynamic), plug-ins, and drivers
+- H. Clean room design (somebody explains you the code but you have never seen it)
+- I. You read a paper, understand algorithm, write own code
+
+```{solution}
+- Derivative work: A-F
+- Not derivative work: G-I
+- E and F: This depends on how you do it, see "clean room design".
+```
+:::
+
+:::{exercise} Exercise: Licensing situations
+
+Consider some common licensing situations. If you are part of an exercise
+group, discuss these with others:
+1. What is the StackOverflow license for code you copy and paste?
+2. A journal requests that you release your software during publication. You have
+ copied a portion of the code from another package, which you have forgotten.
+ Can you satisfy the journal's request?
+3. You want to fix a bug in a project someone else has released, but there is no license. What risks are there?
+4. How would you ask someone to add a license?
+5. You incorporate MIT, GPL, and BSD3 licensed code into your project. What possible licenses can you pick for your project?
+6. You do the same as above but add in another license that looks strong copyleft. What possible licenses can you use now?
+7. Do licenses apply if you don't distribute your code? Why or why not?
+8. Which licenses are most/least attractive for companies with proprietary software?
+
+```{solution}
+1. As indicated [here](https://stackoverflow.com/help/licensing), all publicly accessible user contributions are licensed under [Creative Commons Attribution-ShareAlike](https://creativecommons.org/licenses/by-sa/4.0/) license. See Stackoverflow [Terms of service](https://stackoverflow.com/legal/terms-of-service/public#licensing) for more detailed information.
+2. "Standard" licensing rules apply. So in this case, you would need to remove the portion of code you have copied from another package before being able to release your software.
+3. By default you are no authorized to use the content of a repository when there is no license. And derivative work is also not possible by default. Other risks: it may not be clear whether you can use and distribute (publish) the bugfixed code. For the repo owners it may not be clear whether they can use and distributed the bugfixed code. However, the authors may have forgotten to add a license so we suggest you to contact the authors (e.g. make an issue) and ask whether they are willing to add a license.
+4. As mentionned in 3., the easiest is to fill an issue and explain the reasons why you would like to use this software (or update it).
+5. Combining software with different licenses can be tricky and it is important to understand compatibilities (or lack of compatibilities) of the various licenses. GPL license is the most protective (BSD and MIT are quite permissive) so for the resulting combined software you could use a GPL license. However, re-licensing may not be necessary.
+6. Derivative work would need to be shared under this strong copyleft license (e.g. AGPL or GPL), unless the components are only plugins or libraries.
+7. If you keep your code for yourself, you may think you do not need a license. However, remember that in most companies/universities, your employer is "owning" your work and when you leave you may not be allowed to "distribute your code to your future self". So the best is always to add a license!
+8. The least attractive licenses for companies with proprietary software are licenses where you would need to keep an open license when creating derivative work. For instance GPL and and AGPL. The most attractive licenses are permissive licenses where they can reuse, modify and relicense with no conditions. For instance MIT, BSD and Apache License.
+```
+:::
diff --git a/branch/main/_sources/testing.md.txt b/branch/main/_sources/testing.md.txt
new file mode 100644
index 0000000..35adffa
--- /dev/null
+++ b/branch/main/_sources/testing.md.txt
@@ -0,0 +1,222 @@
+# Automated testing
+
+:::{objectives}
+- Know **where to start** in your own project.
+- Know what possibilities and techniques are available in the Python world.
+- Have an example for how to make the **testing part of code review**.
+:::
+
+:::{instructor-note}
+- (15 min) Motivation
+- (15 min) End-to-end tests
+- (15 min) Pytest
+- (15 min) Adding the unit test to GitHub Actions
+- (10 min) What else is possible
+- (20 min) Exercise
+:::
+
+
+## Motivation
+
+Testing is a way to check that the code does what it is expected to.
+
+- **Less scary to change code**: tests will tell you whether something broke.
+- **Easier for new people** to join.
+- Easier for somebody to **revive an old code**.
+- **End-to-end test**: run the whole code and compare result to a reference.
+- **Unit tests**: test one unit (function or module). Can guide towards better
+ structured code: complicated code is more difficult to test.
+
+
+## How testing is often taught
+
+```python
+def add(a, b):
+ return a + b
+
+
+def test_add():
+ assert add(1, 2) == 3
+```
+
+How this feels:
+:::{figure} img/owl.png
+:alt: Instruction on how to draw an owl
+:width: 50%
+:class: with-border
+
+[Citation needed]
+:::
+
+Instead, we will look at and **discuss a real example** where we test components
+from our example project.
+
+
+## Where to start
+
+**Do I even need testing?**:
+- A simple script or notebook probably does not need an automated test.
+
+**If you have nothing yet**:
+- Start with an end-to-end test.
+- Describe in words how *you* check whether the code still works.
+- Translate the words into a script (any language).
+- Run the script automatically on every code change (GitHub Actions or GitLab CI).
+
+**If you want to start with unit-testing**:
+- You want to rewrite a function? Start adding a unit test right there first.
+- You spend few days chasing a bug? Once you fix it, add a test to make sure it does not come back.
+
+
+## End-to-end tests
+
+- This is our end-to-end test:
+- Note how we can run it [on GitHub automatically](https://github.com/workshop-material/classification-task/blob/d5baee6a7600986b5fccc2fca4ee80a90c2d5f69/.github/workflows/test.yml#L28).
+- Also browse .
+- If we have time, we can try to create a pull request which would break the
+ code and see how the test fails.
+
+
+## Pytest
+
+Here is a simple example of a test:
+```{code-block} python
+---
+emphasize-lines: 10-14
+---
+def fahrenheit_to_celsius(temp_f):
+ """Converts temperature in Fahrenheit
+ to Celsius.
+ """
+ temp_c = (temp_f - 32.0) * (5.0/9.0)
+ return temp_c
+
+
+# this is the test function
+def test_fahrenheit_to_celsius():
+ temp_c = fahrenheit_to_celsius(temp_f=100.0)
+ expected_result = 37.777777
+ # assert raises an error if the condition is not met
+ assert abs(temp_c - expected_result) < 1.0e-6
+```
+
+To run the test(s):
+```console
+$ pytest example.py
+```
+
+Explanation: `pytest` will look for functions starting with `test_` in files
+and directories given as arguments. It will run them and report the results.
+
+Good practice to add unit tests:
+- Add the test function and run it.
+- Break the function on purpose and run the test.
+- Does the test fail as expected?
+
+
+## Adding the unit test to GitHub Actions
+
+Our next goal is that we want GitHub to run the unit test
+automatically on every change.
+
+First we need to extend our
+[environment.yml](https://github.com/workshop-material/classification-task/blob/main/environment.yml):
+```{code-block} yaml
+---
+emphasize-lines: 12
+---
+name: classification-task
+channels:
+ - conda-forge
+dependencies:
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - pytest
+```
+
+We also need to extend `.github/workflows/test.yml` (highlighted line):
+```{code-block} yaml
+---
+emphasize-lines: 29
+---
+name: Test
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+ runs-on: ubuntu-24.04
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - uses: mamba-org/setup-micromamba@v1
+ with:
+ micromamba-version: '2.0.5-0' # any version from https://github.com/mamba-org/micromamba-releases
+ environment-file: environment.yml
+ init-shell: bash
+ cache-environment: true
+ post-cleanup: 'all'
+ generate-run-shell: false
+
+ - name: Run tests
+ run: |
+ ./test.sh
+ pytest generate-predictions.py
+ shell: bash -el {0}
+```
+
+In the above example, we assume that we added a test function to `generate-predictions.py`.
+
+If we have time, we can try to create a pull request which would break the
+code and see how the test fails.
+
+
+## What else is possible
+
+- Run the test set **automatically** on every code change:
+ - [GitHub Actions](https://github.com/features/actions)
+ - [GitLab CI](https://docs.gitlab.com/ee/ci/)
+
+- The testing above used **example-based** testing.
+
+- **Test coverage**: how much of the code is traversed by tests?
+ - Python: [pytest-cov](https://pytest-cov.readthedocs.io/)
+ - Result can be deployed to services like [Codecov](https://about.codecov.io/) or [Coveralls](https://coveralls.io/).
+
+- **Property-based** testing: generates arbitrary data matching your specification and checks that your guarantee still holds in that case.
+ - Python: [hypothesis](https://hypothesis.readthedocs.io/)
+
+- **Snapshot-based** testing: makes it easier to generate snapshots for regression tests.
+ - Python: [syrupy](https://syrupy-project.github.io/syrupy/)
+
+- **Mutation testing**: tests pass -> change a line of code (make a mutant) -> test again and check whether all mutants get "killed".
+ - Python: [mutmut](https://mutmut.readthedocs.io/)
+
+
+## Exercises
+
+:::{exercise}
+Experiment with the example project and what we learned above or try it **on
+the example project or on your own project**:
+- Add a unit test. **If you are unsure where to start**, you can try to move
+ [the majority
+ vote](https://github.com/workshop-material/classification-task/blob/79ce3be8fc187afbc33c91c11ea7003ce9bf56cd/generate_predictions.py#L28)
+ into a separate function and write a test function for it.
+- Try to run pytest locally.
+- Check whether it fails when you break the corresponding function.
+- Try to run it on GitHub Actions.
+- Create a pull request which would break the code and see whether the automatic test would catch it.
+- Try to design an end-to-end test for your project. Already the thought
+ process can be very helpful.
+:::
diff --git a/branch/main/_sources/version-control.md.txt b/branch/main/_sources/version-control.md.txt
new file mode 100644
index 0000000..096f091
--- /dev/null
+++ b/branch/main/_sources/version-control.md.txt
@@ -0,0 +1,13 @@
+# Introduction to version control with Git and GitHub
+
+```{toctree}
+:maxdepth: 1
+
+version-control/motivation.md
+version-control/browsing.md
+version-control/branching-and-committing.md
+version-control/merging.md
+version-control/conflict-resolution.md
+version-control/practical-advice.md
+version-control/sharing.md
+```
diff --git a/branch/main/_sources/version-control/branching-and-committing.md.txt b/branch/main/_sources/version-control/branching-and-committing.md.txt
new file mode 100644
index 0000000..3b9218d
--- /dev/null
+++ b/branch/main/_sources/version-control/branching-and-committing.md.txt
@@ -0,0 +1,348 @@
+# Creating branches and commits
+
+The first and most basic task to do in Git is **record changes** using
+commits. In this part, we will record changes in two
+ways: on a **new branch** (which supports multiple lines of work at once), and directly
+on the "main" branch (which happens to be the default branch here).
+
+:::{objectives}
+- Record new changes to our own copy of the project.
+- Understand adding changes in two separate branches.
+- See how to compare different versions or branches.
+:::
+
+
+## Background
+
+- In the previous episode we have browsed an existing **repository** and saw **commits**
+ and **branches**.
+- Each **commit** is a snapshot of the entire project at a certain
+ point in time and has a unique identifier (**hash**) .
+- A **branch** is a line of development, and the `main` branch or `master` branch
+ are often the default branch in Git.
+- A branch in Git is like a **sticky note that is attached to a commit**. When we add
+ new commits to a branch, the sticky note moves to the new commit.
+- **Tags** are a way to mark a specific commit as important, for example a release
+ version. They are also like a sticky note, but they don't move when new
+ commits are added.
+
+:::{figure} img/gophers.png
+:alt: Branching explained with a gopher
+:width: 100%
+
+What if two people, at the same time, make two different changes? Git
+can merge them together easily. Image created using
+([inspiration](https://twitter.com/jay_gee/status/703360688618536960)).
+:::
+
+
+## Exercise: Creating branches and commits
+
+:::{figure} img/illustrations/branches.png
+:alt: Illustration of what we want to achieve in this exercise
+:width: 60%
+
+Illustration of what we want to achieve in this exercise.
+:::
+
+:::{exercise} Exercise: Practice creating commits and branches (20 min)
+1. First create a new branch and then either add a new file or modify an
+ existing file and commit the change. Make sure that you now work **on your
+ copy** of the example repository. In your new commit you can share a Git or
+ programming trick you like or improve the documentation.
+1. In a new commit, modify the file again.
+1. Switch to the `main` branch and create a commit there.
+1. Browse the network and locate the commits that you just created ("Insights" -> "Network").
+1. Compare the branch that you created with the `main` branch. Can you find an easy way to see the differences?
+1. Can you find a way to compare versions between two arbitrary commits in the repository?
+1. Try to rename the branch that you created and then browse the network again.
+1. Try to create a tag for one of the commits that you created (on GitHub,
+ create a "release").
+1. Optional: If this was too easy, you can try to create a new branch and on
+ this branch work on one of these new features:
+ - The random seed is now set to a specific number (42). Make it possible to
+ set the seed to another number or to turn off the seed setting via the command line interface.
+ - Move the code that does the majority vote to an own function.
+ - Write a test for the new majority vote function.
+ - The `num_neighbors` in the code needs to be odd. Change the code so that it stops with an error message if an even number is given.
+ - Add type annotations to some functions.
+ - When calling the `scatter_plot` function, call the function with named arguments.
+ - Add example usage to README.md.
+ - Add a Jupyter Notebook version of the example code.
+:::
+
+The solution below goes over most of the answers, and you are
+encouraged to use it when the hints aren't enough - this is by
+design.
+
+
+## Solution and walk-through
+
+
+### (1) Create a new branch and a new commit
+
+:::::{tabs}
+::::{group-tab} GitHub
+1. Where it says "main" at the top left, click, enter a new branch
+ name (e.g. `new-tutorial`), then click on
+ "Create branch ... from main".
+1. Make sure you are still on the `new-tutorial` branch (it should say
+ it at the top), and click "Add file" → "Create new file" from the
+ upper right.
+1. Enter a filename where it says "Name your file...".
+1. Share some Git or programming trick you like.
+1. Click "Commit changes"
+1. Enter a commit message. Then click "Commit
+ changes".
+
+You should appear back at the file browser view, and see your
+modification there.
+::::
+
+::::{group-tab} VS Code
+1. Make sure that you are on the main branch.
+1. Version control button on left sidebar → Three dots in upper right of source control → Branch → Create branch.
+1. VS Code automatically switches to the new branch.
+4. Create a new file.
+4. In the version control sidebar, click the `+` sign to add the file for the next commit.
+4. Enter a brief message and click "Commit".
+::::
+
+::::{group-tab} Command line
+Create a new branch called `new-tutorial` from `main` and switch to it:
+```console
+$ git switch --create new-tutorial main
+```
+
+Then create the new file. Finally add and commit the file:
+```console
+$ git add tutorial.md # or a different file name
+$ git commit -m "sharing a programming trick"
+```
+::::
+:::::
+
+
+### (2) Modify the file again with a new commit
+
+:::::{tabs}
+::::{group-tab} GitHub
+This is similar to before, but we click on the existing file to
+modify.
+
+1. Click on the file you added or modified previously.
+2. Click the edit button, the pencil icon at top-right.
+3. Follow the "Commit changes" instructions as in the previous step.
+::::
+
+::::{group-tab} VS Code
+Repeat as in the previous step.
+::::
+
+::::{group-tab} Command line
+Modify the file. Then commit the new change:
+```console
+$ git add tutorial.md
+$ git commit -m "short summary of the change"
+```
+
+Make sure to replace "short summary of the change" with a meaningful commit
+message.
+::::
+:::::
+
+
+### (3) Switch to the main branch and create a commit there
+
+:::::{tabs}
+::::{group-tab} GitHub
+1. Go back to the main repository page (your user's page).
+1. In the branch switch view (top left above the file view), switch to
+ `main`.
+1. Modify another file that already exists, following the pattern
+ from above.
+::::
+
+::::{group-tab} VS Code
+Use the branch selector at the bottom to switch back to the main branch. Repeat the same steps as above,
+but this time modify a different file.
+::::
+
+::::{group-tab} Command line
+First switch to the `main` branch:
+```console
+$ git switch main
+```
+
+Then modify a file. Finally `git add` and then commit the change:
+```console
+$ git commit -m "short summary of the change"
+```
+::::
+:::::
+
+
+### (4) Browse the commits you just made
+
+Let's look at what we did. Now, the `main` and the new branches
+have diverged: both have some modifications. Try to find the commits
+you created.
+
+:::::{tabs}
+::::{group-tab} GitHub
+Insights tab → Network view (just like we have done before).
+::::
+
+::::{group-tab} VS Code
+This requires an extension. Opening the VS Code terminal lets you use the
+command line method (View → Terminal will open a terminal at bottom). This is
+a normal command line interface and very useful for work.
+::::
+
+::::{group-tab} Command line
+```console
+$ git graph
+$ git log --graph --oneline --decorate --all # if you didn't define git graph yet.
+```
+::::
+:::::
+
+
+### (5) Compare the branches
+
+Comparing changes is an important thing we need to do. When using the
+GitHub view only, this may not be so common, but we'll show it so that
+it makes sense later on.
+
+:::::{tabs}
+
+::::{group-tab} GitHub
+A nice way to compare branches is to add `/compare` to the URL of the repository,
+for example (replace USER):
+https://github.com/**USER**/classification-task/compare
+::::
+
+::::{group-tab} VS Code
+This seems to require an extension. We recommend you use the command line method.
+::::
+
+::::{group-tab} Command line
+```console
+$ git diff main new-tutorial
+```
+
+Try also the other way around:
+```console
+$ git diff new-tutorial main
+```
+
+Try also this if you only want to see the file names that are different:
+```console
+$ git diff --name-only main new-tutorial
+```
+::::
+:::::
+
+
+### (6) Compare two arbitrary commits
+
+This is similar to above, but not only between branches.
+
+:::::{tabs}
+::::{group-tab} GitHub
+Following the `/compare`-trick above, one can compare commits on GitHub by
+adjusting the following URL:
+https://github.com/**USER**/classification-task/compare/**VERSION1**..**VERSION2**
+
+Replace `USER` with your username and `VERSION1` and `VERSION2` with a commit hash or branch name.
+Please try it out.
+::::
+
+::::{group-tab} VS Code
+Again, we recommend using the Command Line method.
+::::
+
+::::{group-tab} Command line
+First try this to get a short overview of the commits:
+```console
+$ git log --oneline
+```
+
+Then try to compare any two commit identifiers with `git diff`.
+::::
+:::::
+
+
+### (7) Renaming a branch
+
+:::::{tabs}
+::::{group-tab} GitHub
+
+Branch button → View all branches → three dots at right side → Rename branch.
+
+::::
+::::{group-tab} VS Code
+Version control sidebar → Three dots (same as in step 2) → Branch → Rename branch. Make sure you are on the right branch before you start.
+::::
+
+::::{group-tab} Command line
+Renaming the current branch:
+```console
+$ git branch -m new-branch-name
+```
+
+Renaming a different branch:
+```console
+$ git branch -m different-branch new-branch-name
+```
+::::
+:::::
+
+
+### (8) Creating a tag
+
+Tags are a way to mark a specific commit as important, for example a release
+version. They are also like a sticky note, but they don't move when new
+commits are added.
+
+:::::{tabs}
+::::{group-tab} GitHub
+On the right side, below "Releases", click on "Create a new release".
+
+What GitHub calls releases are actually tags in Git with additional metadata.
+For the purpose of this exercise we can use them interchangeably.
+::::
+
+::::{group-tab} VS Code
+Version control sidebar → Three dots (same as in step 2) → Tags → Create tag. Make sure you are on the expected commit before you do this.
+::::
+
+::::{group-tab} Command line
+Creating a tag:
+```console
+$ git tag -a v1.0 -m "New manuscript version for the pre-print"
+```
+::::
+:::::
+
+
+## Summary
+
+In this part, we saw how we can make changes to our files. With branches, we
+can track several lines of work at once, and can compare their differences.
+
+- You could commit directly to `main` if there is only one single line
+ of work and it's only you.
+- You could commit to branches if there are multiple lines of work at
+ once, and you don't want them to interfere with each other.
+- Tags are useful to mark a specific commit as important, for example a
+ release version.
+- In Git, commits form a so-called "graph". Branches are tags in Git function
+ like sticky notes that stick to specific commits. What this means for us is
+ that it does not cost any significant disk space to create new branches.
+- Not all files should be added to Git. For example, temporary files or
+ files with sensitive information or files which are generated as part of
+ the build process should not be added to Git. For this we use
+ `.gitignore` (more about this later: {doc}`practical-advice`).
+- Unsure on which branch you are or what state the repository is in?
+ On the command line, use `git status` frequently to get a quick overview.
diff --git a/branch/main/_sources/version-control/browsing.md.txt b/branch/main/_sources/version-control/browsing.md.txt
new file mode 100644
index 0000000..f70c0ac
--- /dev/null
+++ b/branch/main/_sources/version-control/browsing.md.txt
@@ -0,0 +1,373 @@
+# Forking, cloning, and browsing
+
+In this episode, we will look at an **existing repository** to understand how
+all the pieces work together. Along the way, we will make a copy (by
+**forking** and/or **cloning**) of the repository for us, which will be used
+for our own changes.
+
+:::{objectives}
+- See a real Git repository and understand what is inside of it.
+- Understand how version control allows advanced inspection of a
+ repository.
+- See how Git allows multiple people to work on the same project at the same time.
+- **See the big picture** instead of remembering a bunch of commands.
+:::
+
+
+## GitHub, VS Code, or command line
+
+We offer **three different paths** for this exercise:
+- **GitHub** (this is the one we will demonstrate)
+- **VS Code** (if you prefer to follow along using an editor)
+- **Command line** (for people comfortable with the command line)
+
+
+## Creating a copy of the repository by "forking" or "cloning"
+
+A **repository** is a collection of files in one directory tracked by Git. A
+GitHub repository is GitHub's copy, which adds things like access control,
+issue tracking, and discussions. Each GitHub repository is owned by a user or
+organization, who controls access.
+
+First, we need to make **our own copy** of the exercise repository. This will
+become important later, when we make our own changes.
+
+:::{figure} img/illustrations/fork.png
+:alt: Illustration of forking a repository on GitHub
+:width: 50%
+
+Illustration of forking a repository on GitHub.
+:::
+
+:::{figure} img/illustrations/clone.png
+:alt: Illustration of cloning a repository to your computer
+:width: 50%
+
+Illustration of cloning a repository to your computer.
+:::
+
+:::{figure} img/illustrations/clone-of-fork.png
+:alt: Illustration of cloning a forked repository to your computer
+:width: 60%
+
+It is also possible to do this: to clone a forked repository to your computer.
+:::
+
+At all times you should be aware of if you are looking at **your repository**
+or the **upstream repository** (original repository):
+- Your repository: https://github.com/**USER**/classification-task
+- Upstream repository: https://github.com/**workshop-material**/classification-task
+
+:::{admonition} How to create a fork
+1. Go to the repository view on GitHub:
+1. First, on GitHub, click the button that says "Fork". It is towards
+ the top-right of the screen.
+1. You should shortly be redirected to your copy of the repository
+ **USER/classification-task**.
+:::
+
+:::{instructor-note}
+Before starting the exercise session show
+how to fork the repository to own account
+(above).
+:::
+
+
+
+## Exercise: Copy and browse an existing project
+
+Work on this by yourself or in pairs.
+
+
+
+
+
+
+
+
+
+
+
+
+::::::{prereq} Exercise preparation
+:::::{tabs}
+::::{group-tab} GitHub
+In this case you will work on a fork.
+
+You only need to open your own view, as described above. The browser
+URL should look like https://github.com/**USER**/classification-task, where
+USER is your GitHub username.
+::::
+
+::::{group-tab} VS Code
+You need to have forked the repository as described above.
+
+We need to start by making a clone of this repository so that you can work
+locally.
+
+1. Start VS Code.
+1. If you don't have the default view (you already have a project
+open), go to File → New Window.
+1. Under "Start" on the screen, select "Clone Git Repository...". Alternatively
+ navigate to the "Source Control" tab on the left sidebar and click on the "Clone Repository" button.
+1. Paste in this URL: `https://github.com/USER/classification-task`, where
+ `USER` is your username. You can copy this from the browser.
+1. Browse and select the folder in which you want to clone the
+ repository.
+1. Say yes, you want to open this repository.
+1. Select "Yes, I trust the authors" (the other option works too).
+::::
+
+::::{group-tab} Command line
+**This path is advanced and we do not include all command line
+information: you need to be somewhat
+comfortable with the command line already.**
+
+You need to have forked the repository as described above.
+
+We need to start by making a clone of this repository so that you can work
+locally.
+
+1. Start the terminal in which you use Git (terminal application, or
+ Git Bash).
+1. Change to the directory where you would want the repository to be
+ (`cd ~/code` for example, if the `~/code` directory is where you
+ store your files).
+1. Run the following command: `git clone
+ https://github.com/USER/classification-task`, where `USER` is your
+ username. You might need to use a SSH clone command instead of
+ HTTPS, depending on your setup.
+1. Change to that directory: `cd classification-task`
+::::
+:::::
+::::::
+
+
+:::{exercise} Exercise: Browsing an existing project (20 min)
+
+Browse the [example project](https://github.com/workshop-material/classification-task) and
+explore commits and branches, either on a fork or on a clone. Take notes and
+prepare questions. The hints are for the GitHub path in the browser.
+
+1. Browse the **commit history**: Are commit messages understandable?
+ (Hint: "Commit history", the timeline symbol, above the file list)
+1. Compare the commit history with the **network graph** ("Insights" -> "Network"). Can you find the branches?
+1. Try to find the **history of commits for a single file**, e.g. `generate_predictions.py`.
+ (Hint: "History" button in the file view)
+1. **Which files include the word "training"**?
+ (Hint: the GitHub search on top of the repository view)
+1. In the `generate_predictions.py` file,
+ find out who modified the evaluation of "majority_index"
+ last and **in which commit**.
+ (Hint: "Blame" view in the file view)
+1. Can you use this code yourself? **Are you allowed to share
+ modifications**?
+ (Hint: look for a license file)
+:::
+
+The solution below goes over most of the answers, and you are
+encouraged to use it when the hints aren't enough - this is by
+design.
+
+
+## Solution and walk-through
+
+### (1) Basic browsing
+
+The most basic thing to look at is the history of commits.
+
+* This is visible from a button in the repository view. We see every
+ change, when, and who has committed.
+* Every change has a unique identifier, such as `79ce3be`. This can
+ be used to identify both this change, and the whole project's
+ version as of that change.
+* Clicking on a change in the view shows more.
+
+:::::{tabs}
+
+::::{group-tab} GitHub
+Click on the timeline symbol in the repository view:
+ :::{figure} img/browsing/history.png
+ :alt: Screenshot on GitHub of where to find the commit history
+ :width: 100%
+ :class: with-border
+ :::
+::::
+
+::::{group-tab} VS Code
+This can be done from "Timeline", in the bottom of explorer, but only
+for a single file.
+::::
+
+::::{group-tab} Command line
+Run:
+```console
+$ git log
+```
+
+Try also:
+```console
+$ git log --oneline
+```
+::::
+
+:::::
+
+
+### (2) Compare commit history with network graph
+
+The commit history we saw above looks linear: one commit after
+another. But if we look at the network view, we see some branching and merging points.
+We'll see how to do these later. This is another one of the
+basic Git views.
+
+:::::{tabs}
+::::{group-tab} GitHub
+In a new browser tab, open the "Insights" tab, and click on "Network".
+You can hover over the commit dots to see the person who committed and
+how they correspond with the commits in the other view:
+ :::{figure} img/browsing/network.png
+ :alt: Screenshot on GitHub of the network graph
+ :width: 100%
+ :class: with-border
+ :::
+::::
+
+::::{group-tab} VS Code
+We don't know how to do this without an extension. Try starting a terminal and using the
+"Command line" option.
+::::
+
+::::{group-tab} Command line
+This is a useful command to browse the network of commits locally:
+```console
+$ git log --graph --oneline --decorate --all
+```
+
+To avoid having to type this long command every time, you can define an alias (shortcut):
+```console
+$ git config --global alias.graph "log --graph --oneline --decorate --all"
+```
+
+From then on, you can use `git graph` to see the network graph.
+::::
+
+:::::
+
+
+### (3) How can you browse the history of a single file?
+
+We see the history for the whole repository, but we can also see it
+for a single file.
+
+:::::{tabs}
+
+::::{group-tab} GitHub
+Navigate to the file view: Main page → generate_predictions.py.
+Click the "History" button near the top right.
+::::
+
+::::{group-tab} VS Code
+Open generate_predictions.py file in the editor. Under the file browser,
+we see a "Timeline" view there.
+::::
+
+::::{group-tab} Command line
+The `git log` command can take a filename and provide the log of only
+a single file:
+
+```
+$ git log generate_predictions.py
+```
+::::
+
+:::::
+
+
+### (4) Which files include the word "training"?
+
+Version control makes it very easy to find all occurrences of a
+word or pattern. This is useful for things like finding where functions or
+variables are defined or used.
+
+:::::{tabs}
+::::{group-tab} GitHub
+We go to the main file view. We click the Search magnifying
+class at the very top, type "training", and click enter. We see every
+instance, including the context.
+
+:::{admonition} Searching in a forked repository will not work instantaneously!
+
+It usually takes a few minutes before one can search for keywords in a forked repository
+since it first needs to build the search index the very first time we search.
+Start it, continue with other steps, then come back to this.
+:::
+::::
+
+::::{group-tab} VS Code
+If you use the "Search" magnifying class on the left sidebar, and
+search for "training" it shows the occurrences in every file. You can
+click to see the usage in context.
+::::
+
+::::{group-tab} Command line
+`grep` is the command line tool that searches for lines matching a term
+```console
+$ git grep training # only the lines
+$ git grep -C 3 training # three lines of context
+$ git grep -i training # case insensitive
+```
+::::
+
+:::::
+
+
+### (5) Who modified a particular line last and when?
+
+This is called the "annotate" or "blame" view. The name "blame"
+is very unfortunate, but it is the standard term for historical reasons
+for this functionality and it is not meant to blame anyone.
+
+:::::{tabs}
+
+::::{group-tab} GitHub
+From a file view, change preview to "Blame" towards the top-left.
+To get the actual commit, click on the commit message next
+to the code line that you are interested in.
+::::
+
+::::{group-tab} VS Code
+This requires an extension. We recommend for now you use the command
+line version, after opening a terminal.
+::::
+
+::::{group-tab} Command line
+These two commands are similar but have slightly different output.
+```console
+$ git annotate generate_predictions.py
+$ git blame generate_predictions.py
+```
+::::
+
+:::::
+
+
+### (6) Can you use this code yourself? Are you allowed to share modifications?
+
+- Look at the file `LICENSE`.
+- On GitHub, click on the file to see a nice summary of what we can do with this:
+ :::{figure} img/browsing/license.png
+ :alt: Screenshot on GitHub summarizing license terms
+ :width: 100%
+ :class: with-border
+ :::
+
+
+### Summary
+
+- Git allowed us to understand this simple project much better than we
+ could, if it was just a few files on our own computer.
+- It was easy to share the project with the course.
+- By forking the repository, we created our own copy. This is
+ important for the following, where we will make changes to
+ our copy.
diff --git a/branch/main/_sources/version-control/conflict-resolution.md.txt b/branch/main/_sources/version-control/conflict-resolution.md.txt
new file mode 100644
index 0000000..a17b283
--- /dev/null
+++ b/branch/main/_sources/version-control/conflict-resolution.md.txt
@@ -0,0 +1,60 @@
+# Conflict resolution
+
+
+## Resolving a conflict (demonstration)
+
+A conflict is when Git asks humans to decide during a merge which of two
+changes to keep **if the same portion of a file has been changed in two
+different ways on two different branches**.
+
+We will practice conflict resolution in the collaborative Git lesson (next
+day).
+
+Here we will only demonstrate how to create a conflict and how to resolve it,
+**all on GitHub**. Once we understand how this works, we will be more confident
+to resolve conflicts also in the **command line** (we can demonstrate this if
+we have time).
+
+How to create a conflict (please try this in your own time *and just watch now*):
+- Create a new branch from `main` and on it make a change to a file.
+- On `main`, make a different change to the same part of the same file.
+- Now try to merge the new branch to `main`. You will get a conflict.
+
+How to resolve conflicts:
+- On GitHub, you can resolve conflicts by clicking on the "Resolve conflicts"
+ button. This will open a text editor where you can choose which changes to
+ keep.
+ Make sure to remove the conflict markers.
+ After resolving the conflict, you can commit the changes and merge the
+ pull request.
+- Sometimes a conflict is between your change and somebody else's change. In
+ that case, you might have to discuss with the other person which changes to
+ keep.
+
+
+## Avoiding conflicts
+
+```{discussion} The human side of conflicts
+- What does it mean if two people do the same thing in two different ways?
+- What if you work on the same file but do two different things in the different sections?
+- What if you do something, don't tell someone from 6 months, and then try to combine it with other people's work?
+- How are conflicts avoided in other work? (Only one person working at once?
+ Declaring what you are doing before you start, if there is any chance someone
+ else might do the same thing, helps.)
+```
+
+- Human measures
+ - Think and plan to which branch you will commit to.
+ - Do not put unrelated changes on the same branch.
+- Collaboration measures
+ - Open an issue and discuss with collaborators before starting a long-living
+ branch.
+- Project layout measures
+ - Modifying global data often causes conflicts.
+ - Modular programming reduces this risk.
+- Technical measures
+ - **Share your changes early and often**: This is one of the happy,
+ rare circumstances when everyone doing the selfish thing (publishing your
+ changes as early as practical) results in best case for everyone!
+ - Pull/rebase often to keep up to date with upstream.
+ - Resolve conflicts early.
diff --git a/branch/main/_sources/version-control/merging.md.txt b/branch/main/_sources/version-control/merging.md.txt
new file mode 100644
index 0000000..6c95c08
--- /dev/null
+++ b/branch/main/_sources/version-control/merging.md.txt
@@ -0,0 +1,372 @@
+# Merging changes and contributing to the project
+
+Git allows us to have different development lines where we can try things out.
+It also allows different people to work on the same project at the same. This
+means that we have to somehow combine the changes later. In this part we will
+practice this: **merging**.
+
+:::{objectives}
+- Understand that on GitHub merging is done through a **pull request** (on GitLab: "merge request"). Think
+ of it as a **change proposal**.
+- Create and merge a pull request within your own repository.
+- Understand (and optionally) do the same across repositories, to contribute to
+ the upstream public repository.
+:::
+
+
+## Exercise
+
+:::{figure} img/illustrations/merging.png
+:alt: Illustration of what we want to achieve in this exercise
+:width: 60%
+
+Illustration of what we want to achieve in this exercise.
+:::
+
+
+::::::{exercise} Exercise: Merging branches
+:::::{tabs}
+::::{group-tab} GitHub
+
+First, we make something called a pull request, which allows
+review and commenting before the actual merge.
+
+We assume that in the previous exercise you have created a new branch with one
+or few new commits. We provide basic hints. You should refer to the solution
+as needed.
+
+1. Navigate to your branch from the previous episode
+ (hint: the same branch view we used last time).
+
+1. Begin the pull request process
+ (hint: There is a "Contribute" button in the branch view).
+
+1. Add or modify the pull request title and description, and verify the other data.
+ In the pull request verify the target repository and the target
+ branch. Make sure that you are merging within your own repository.
+ **GitHub: By default, it will offer to make the change to the
+ upstream repository, `workshop-material`. You should change this**, you
+ shouldn't contribute your commit(s) upstream yet. Where it says
+ `base repository`, select your own repository.
+
+1. Create the pull request by clicking "Create pull request". Browse
+ the network view to see if anything has changed yet.
+
+1. Merge the pull request, or if you are not on GitHub you can merge
+ the branch locally. Browse the network again. What has changed?
+
+1. Find out which branches are merged and thus safe to delete. Then remove them
+ and verify that the commits are still there, only the branch labels are
+ gone (hint: you can delete branches that have been merged into `main`).
+
+1. Optional: Try to create a new branch with a new change, then open a pull
+ request but towards the original (upstream) repository. We will later merge few of
+ those.
+::::
+
+::::{group-tab} Local (VS Code, Command line)
+
+When working locally, it's easier to merge branches: we can just do
+the merge, without making a pull request. But we don't have that step
+of review and commenting and possibly adjusting.
+
+1. Switch to the `main` branch that you want to merge the *other*
+ branch into. (Note that this is the other way around from the
+ GitHub path).
+
+Then:
+
+5. Merge the other branch into `main` (which is then your current branch).
+
+6. Find out which branches are merged and thus safe to delete. Then remove them
+ and verify that the commits are still there, only the branch labels are
+ gone. (Hint: you can delete branches that have been merged into `main`).
+
+7. Optional: Try to create a new branch, and make a
+ GitHub pull request with your change, and contribute it to our
+ upstream repository.
+::::
+:::::
+::::::
+
+The solution below goes over most of the answers, and you are
+encouraged to use it when the hints aren't enough - this is by design.
+
+
+## Solution and walk-through
+
+### (1) Navigate to your branch
+
+Before making the pull request, or doing a merge, it's important to
+make sure that you are on the right branch. Many people have been
+frustrated because they forgot this!
+
+:::::{tabs}
+::::{group-tab} GitHub
+GitHub will notice a recently changed branch and offer to make a pull request (clicking there will bring you to step 3):
+ :::{figure} img/merging/github-compare-and-pr.png
+ :alt: Screenshot on GitHub suggesting to compare and make a pull request.
+ :width: 80%
+ :class: with-border
+ :::
+
+If the yellow box is not there, make sure you are on the branch you want to
+merge **from**:
+ :::{figure} img/merging/github-navigate-to-branch.png
+ :alt: Screenshot on GitHub where we navigate to the branch we wish to merge.
+ :width: 80%
+ :class: with-border
+ :::
+::::
+
+::::{group-tab} VS Code
+Remember, you need to switch to the `main` branch, the branch we want to merge **to**.
+This is different from the GitHub path.
+::::
+
+::::{group-tab} Command line
+Remember, you need to switch to the `main` branch, the branch we want to merge **to**.
+This is different from the GitHub path:
+```console
+$ git switch main
+```
+::::
+:::::
+
+
+### (2) Begin the pull request process
+
+In GitHub, the pull request is the way we propose to merge two
+branches together. We start the process of making one.
+
+:::::{tabs}
+::::{group-tab} GitHub
+ :::{figure} img/merging/github-contribute.png
+ :alt: Screenshot on GitHub where we get to the pull request process.
+ :width: 80%
+ :class: with-border
+ :::
+::::
+
+::::{group-tab} VS Code
+It is possible to open pull requests from the editor, but
+we don't cover that here.
+
+If you are working locally, continue to step 5.
+::::
+
+::::{group-tab} Command line
+It is possible to open pull requests from the command line, but
+we don't cover that here.
+
+If you are working locally, continue to step 5.
+::::
+:::::
+
+
+### (3) Fill out and verify the pull request
+
+Check that the pull request is directed to the right repository and branch
+and that it contains the changes that you meant to merge.
+
+:::::{tabs}
+::::{group-tab} GitHub
+Things to check:
+- Base repository: this should be your own
+- Title: make it descriptive
+- Description: make it informative
+- Scroll down to see commits: are these the ones you want to merge?
+- Scroll down to see the changes: are these the ones you want to merge?
+ :::{figure} img/merging/github-comparing-changes.png
+ :alt: Screenshot on GitHub where we verify the changes we want to merge.
+ :width: 80%
+ :class: with-border
+
+ This screenshot only shows the top part. If you scroll down, you
+ can see the commits and the changes. We recommend to do this before
+ clicking on "Create pull request".
+ :::
+::::
+
+::::{group-tab} VS Code
+If you are working locally, continue to step 5.
+::::
+
+::::{group-tab} Command line
+If you are working locally, continue to step 5.
+::::
+:::::
+
+
+### (4) Create the pull request
+
+We actually create the pull request. Don't forget to navigate to the Network
+view after opening the pull request. Note that the changes proposed in the
+pull request are not yet merged.
+
+:::::{tabs}
+::::{group-tab} GitHub
+Click on the green button "Create pull request".
+
+If you click on the little arrow next to "Create pull request", you can also
+see the option to "Create draft pull request". This will be interesting later
+when collaborating with others. It allows you to open a pull request that is
+not ready to be merged yet, but you want to show it to others and get feedback.
+::::
+
+::::{group-tab} VS Code
+It is possible to create pull requests from the editor, but
+we don't cover that here.
+
+If you are working locally, continue to step 5.
+::::
+
+::::{group-tab} Command line
+It is possible to create pull requests from the command line, but
+we don't cover that here.
+
+If you are working locally, continue to step 5.
+::::
+:::::
+
+
+### (5) Merge the pull request
+
+Now, we do the actual merging. We see some effects now.
+
+:::::{tabs}
+::::{group-tab} GitHub
+Review it again (commits and changes), and then click "Merge pull request".
+
+After merging, verify the network view. Also navigate then to your "main"
+branch and check that your change is there.
+::::
+
+::::{group-tab} VS Code
+Just like with the command line, when we merge we modify our *current* branch. Verify you are on the `main` branch.
+
+1. Verify current branch at the bottom.
+1. From the version control sidebar → Three dots → Branch → Merge.
+1. In the selector that comes up, choose the branch you want to merge *from*.
+ The commits on that branch will be added to the current branch.
+::::
+
+::::{group-tab} Command line
+On the command line, when we merge, we always modify our *current* branch.
+
+If you are not sure anymore what your current branch is, type:
+```console
+$ git branch
+```
+... or equally useful to see where we are right now:
+```console
+$ git status
+```
+
+In this case we merge the `new-tutorial` branch into our current branch:
+```console
+$ git merge new-tutorial
+```
+::::
+:::::
+
+
+### (6) Delete merged branches
+
+Before deleting branches, first check whether they are merged.
+
+If you delete an un-merged branch, it will be difficult to find the commits
+that were on that branch. If you delete a merged branch, the commits are now
+also part of the branch where we have merged to.
+
+:::::{tabs}
+::::{group-tab} GitHub
+One way to delete the branch is to click on the "Delete branch" button after the pull
+request is merged:
+ :::{figure} img/merging/github-merged.png
+ :alt: Screenshot on GitHub suggesting us to delete a branch after it has been merged.
+ :width: 80%
+ :class: with-border
+ :::
+
+But what if we forgot? Then navigate to the branch view:
+ :::{figure} img/merging/github-branches.png
+ :alt: Screenshot on GitHub where we navigate to the branches view.
+ :width: 80%
+ :class: with-border
+ :::
+
+In the overview we can see that it has been merged and we can delete it.
+::::
+
+::::{group-tab} VS Code
+From the Source Control sidebar → the three dots (as before) → Branch → Delete Branch. Select the branch name to delete.
+::::
+
+::::{group-tab} Command line
+Verify which branches are merged to the current branch:
+```console
+$ git branch --merged
+
+* main
+ new-tutorial
+```
+
+This means that it is safe to delete the `new-tutorial` branch:
+```console
+$ git branch -d new-tutorial
+```
+
+Verify then that the branch is gone but that the commits are still there:
+```console
+$ git branch
+$ git log --oneline
+```
+::::
+:::::
+
+
+### (7) Contribute to the original repository with a pull request
+
+This is an advanced step. We will practice this tomorrow and
+it is OK to skip this at this stage.
+
+:::::{tabs}
+::::{group-tab} GitHub
+Now that you know how to create branches and opening a pull request, try to
+open a new pull request with a new change but this time the base repository
+should be the upstream one.
+
+In other words, you now send a pull request **across repositories**: from your fork
+to the original repository.
+
+Another thing that is different now is that you might not have permissions to
+merge the pull request. We can then together review and browse the pull
+request.
+::::
+
+::::{group-tab} VS Code
+Not described. We will return to this when we discuss collaboration using GitHub.
+
+You would create a new branch locally like above, push it to GitHub to your own
+user's repository, and from there open a pull request towards the upstream
+repository.
+::::
+
+::::{group-tab} Command line
+Not described. We will return to this when we discuss collaboration using GitHub.
+
+You would create a new branch locally like above, push it to GitHub to your own
+user's repository, and from there open a pull request towards the upstream
+repository.
+::::
+:::::
+
+
+## Summary
+
+- We learned how to merge two branches together.
+- When is this useful? This is not only useful to combine development lines in
+ your own work. Being able to merge branches also **forms the basis for collaboration**.
+- Branches which are merged to other branches are safe to delete, since we only
+ delete the "sticky note" next to a commit, not the commits themselves.
diff --git a/branch/main/_sources/version-control/motivation.md.txt b/branch/main/_sources/version-control/motivation.md.txt
new file mode 100644
index 0000000..2fe3347
--- /dev/null
+++ b/branch/main/_sources/version-control/motivation.md.txt
@@ -0,0 +1,116 @@
+# Motivation
+
+:::{objectives}
+- Browse **commits** and **branches** of a Git repository.
+- Remember that commits are like **snapshots** of the repository at a certain
+ point in time.
+- Know the difference between **Git** (something that tracks changes) and
+ **GitHub/GitLab** (a web platform to host Git repositories).
+:::
+
+
+## Why do we need to keep track of versions?
+
+Version control is an answer to the following questions (do you recognize some
+of them?):
+
+- "It broke ... hopefully I have a working version somewhere?"
+
+- "Can you please send me the latest version?"
+
+- "Where is the latest version?"
+
+- "Which version are you using?"
+
+- "Which version have the authors used in the paper I am trying to reproduce?"
+
+- "Found a bug! Since when was it there?"
+
+- "I am sure it used to work. When did it change?"
+
+- "My laptop is gone. Is my thesis now gone?"
+
+
+## Demonstration
+
+- Example repository:
+- Commits are like **snapshots** and if we break something we can go back to a
+ previous snapshot.
+- Commits carry **metadata** about changes: author, date, commit message, and
+ a checksum.
+- **Branches** are like parallel universes where you can experiment with
+ changes without affecting the default branch:
+
+ ("Insights" -> "Network")
+- With version control we can **annotate code**
+ ([example](https://github.com/workshop-material/classification-tas/blame/main/generate_predictions.py)).
+- **Collaboration**: We can fork (make a copy on GitHub), clone (make a copy
+ to our computer), review, compare, share, and discuss.
+- **Code review**: Others can suggest changes using pull requests or merge
+ requests. These can be reviewed and discussed before they are merged.
+ Conceptually, they are similar to "suggesting changes" in Google Docs.
+
+
+## Features: roll-back, branching, merging, collaboration
+
+- **Roll-back**: you can always go back to a previous version and compare
+
+- **Branching and merging**:
+ - Work on different ideas at the same time
+ - You can experiment with an idea and discard it if it turns out to be a bad idea
+ - Different people can work on the same code/project without interfering
+
+:::{figure} img/gophers.png
+:alt: Branching explained with a gopher
+:width: 100%
+
+Image created using
+([inspiration](https://twitter.com/jay_gee/status/703360688618536960)).
+:::
+
+- **Collaboration**: review, compare, share, discuss
+
+- [Example network graph](https://github.com/workshop-material/classification-task/network)
+
+
+## Talking about code
+
+Which of these two is more practical?
+1. "Clone the code, go to the file 'generate_predictions.py', and search for 'majority_index'.
+ Oh! But make sure you use the version from January 2025."
+1. Or I can send you a permalink:
+
+
+## What we typically like to snapshot
+
+- Software (this is how it started but Git/GitHub can track a lot more)
+- Scripts
+- Documents (plain text files much better suitable than Word documents)
+- Manuscripts (Git is great for collaborating/sharing LaTeX or [Quarto](https://quarto.org/) manuscripts)
+- Configuration files
+- Website sources
+- Data
+
+::::{discussion}
+ In this example somebody tried to keep track of versions without a version
+ control system tool like Git. Discuss the following directory listing. What
+ possible problems do you anticipate with this kind of "version control":
+ ```text
+ myproject-2019.zip
+ myproject-2020-february.zip
+ myproject-2021-august.zip
+ myproject-2023-09-19-working.zip
+ myproject-2023-09-21.zip
+ myproject-2023-09-21-test.zip
+ myproject-2023-09-21-myversion.zip
+ myproject-2023-09-21-newfeature.zip
+ ...
+ (100 more files like these)
+ ```
+
+ :::{solution}
+ - Giving a version to a collaborator and merging changes later with own
+ changes sounds like lots of work.
+ - What if you discover a bug and want to know since when the bug existed?
+ :::
+::::
diff --git a/branch/main/_sources/version-control/practical-advice.md.txt b/branch/main/_sources/version-control/practical-advice.md.txt
new file mode 100644
index 0000000..4832ddd
--- /dev/null
+++ b/branch/main/_sources/version-control/practical-advice.md.txt
@@ -0,0 +1,134 @@
+# Practical advice: How much Git is necessary?
+
+
+## Writing useful commit messages
+
+Useful commit messages **summarize the change and provide context**.
+
+If you need a commit message that is longer than one line,
+then the convention is: one line summarizing the commit, then one empty line,
+then paragraph(s) with more details in free form, if necessary.
+
+Good example:
+```{code-block} text
+---
+emphasize-lines: 1
+---
+increase alpha to 2.0 for faster convergence
+
+the motivation for this change is
+to enable ...
+...
+(more context)
+...
+this is based on a discussion in #123
+```
+
+- **Why something was changed is more important than what has changed.**
+- Cross-reference to issues and discussions if possible/relevant.
+- Bad commit messages: "fix", "oops", "save work"
+- Just for fun, a page collecting bad examples: [http://whatthecommit.com](http://whatthecommit.com)
+- Write commit messages that will be understood
+ 15 years from now by someone else than you. Or by your future you.
+- Many projects start out as projects "just for me" and end up to be successful projects
+ that are developed by 50 people over decades.
+- [Commits with multiple authors](https://help.github.com/articles/creating-a-commit-with-multiple-authors/) are possible.
+
+Good references:
+
+- ["My favourite Git commit"](https://fatbusinessman.com/2019/my-favourite-git-commit)
+- ["On commit messages"](https://who-t.blogspot.com/2009/12/on-commit-messages.html)
+- ["How to Write a Git Commit Message"](https://chris.beams.io/posts/git-commit/)
+
+```{note}
+A great way to learn how to write commit messages and to get inspired by their
+style choices: **browse repositories of codes that you use/like**:
+
+Some examples (but there are so many good examples):
+- [SciPy](https://github.com/scipy/scipy/commits/main)
+- [NumPy](https://github.com/numpy/numpy/commits/main)
+- [Pandas](https://github.com/pandas-dev/pandas/commits/main)
+- [Julia](https://github.com/JuliaLang/julia/commits/master)
+- [ggplot2](https://github.com/tidyverse/ggplot2/commits/main),
+ compare with their [release
+ notes](https://github.com/tidyverse/ggplot2/releases)
+- [Flask](https://github.com/pallets/flask/commits/main),
+ compare with their [release
+ notes](https://github.com/pallets/flask/blob/main/CHANGES.rst)
+
+When designing commit message styles consider also these:
+- How will you easily generate a changelog or release notes?
+- During code review, you can help each other improving commit messages.
+```
+
+But remember: it is better to make any commit, than no commit. Especially in small projects.
+**Let not the perfect be the enemy of the good enough**.
+
+
+## What level of branching complexity is necessary for each project?
+
+**Simple personal projects**:
+- Typically start with just the `main` branch.
+- Use branches for unfinished/untested ideas.
+- Use branches when you are not sure about a change.
+- Use tags to mark important milestones.
+- If you are unsure what to do with unfinished and not working code, commit it
+ to a branch.
+
+**Projects with few persons: you accept things breaking sometimes**
+- It might be reasonable to commit to the `main` branch and feature branches.
+
+**Projects with few persons: changes are reviewed by others**
+- You create new feature branches for changes.
+- Changes are reviewed before they are merged to the `main` branch.
+- Consider to write-protect the `main` branch so that it can only be changed
+ with pull requests or merge requests.
+
+
+## How large should a commit be?
+
+- Better too small than too large (easier to combine than to split).
+- Often I make a commit at the end of the day (this is a unit I would not like to lose).
+- Smaller sized commits may be easier to review for others than huge commits.
+- A commit should not contain unrelated changes to simplify review and possible
+ repair/adjustments/undo later (but again: imperfect commits are better than no commits).
+- Imperfect commits are better than no commits.
+
+
+## Working on the command line? Use "git status" all the time
+
+The `git status` command is one of the most useful commands in Git
+to inform about which branch we are on, what we are about to commit,
+which files might not be tracked, etc.
+
+
+## How about staging and committing?
+
+- Commit early and often: rather create too many commits than too few.
+ You can always combine commits later.
+- Once you commit, it is very, very hard to really lose your code.
+- Always fully commit (or stash) before you do dangerous things, so that you know you are safe.
+ Otherwise it can be hard to recover.
+- Later you can start using the staging area (where you first stage and then commit in a second step).
+- Later start using `git add -p` and/or `git commit -p`.
+
+
+## What to avoid
+
+- Committing **generated files/directories** (example: `__pycache__`, `*.pyc`) ->
+ use [.gitignore
+ files](https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files)
+ ([collection of .gitignore templates](https://github.com/github/gitignore)).
+
+- Committing **huge files** -> use code review to detect this.
+
+- Committing **unrelated changes** together.
+
+- Postponing commits because the changes are "unfinished"/"ugly" -> better ugly
+ commits than no commits.
+
+- When working with branches:
+ - Working on unrelated things on the same branch.
+ - Not updating your branch before starting new work.
+ - Too ambitious branch which risks to never get completed.
+ - Over-engineering the branch layout and safeguards in small projects -> can turn people away from your project.
diff --git a/branch/main/_sources/version-control/sharing.md.txt b/branch/main/_sources/version-control/sharing.md.txt
new file mode 100644
index 0000000..d279aa3
--- /dev/null
+++ b/branch/main/_sources/version-control/sharing.md.txt
@@ -0,0 +1,301 @@
+# Optional: How to turn your project to a Git repo and share it
+
+:::{objectives}
+- Turn our own coding project (small or large, finished or unfinished) into a
+ Git repository.
+- Be able to share a repository on the web to have a backup or so that others
+ can reuse and collaborate or even just find it.
+:::
+
+
+## Exercise
+
+:::{figure} img/illustrations/sharing.png
+:alt: Illustration of how we turn a project into a Git repository and share it
+:width: 100%
+
+From a bunch of files to a local repository which we then share on GitHub.
+:::
+
+
+:::::{exercise} Exercise: Turn your project to a Git repo and share it (20 min)
+
+1. Create a new directory called **myproject** (or a different name) with one or few files in it.
+ This represents our own project. It is not yet a Git repository. You can try
+ that with your own project or use a simple placeholder example.
+2. Turn this new directory into a Git repository.
+3. Share this repository on GitHub (or GitLab, since it really works the same).
+
+We offer **three different paths** of how to do this exercise.
+* Via **GitHub web interface**: easy and can be a good starting point if you are completely
+ new to Git.
+* **VS Code** is quite easy, since VS Code can offer to create the
+ GitHub repositories for you.
+* **Command line**: you need to create the
+ repository on GitHub and link it yourself.
+
+::::{tabs}
+
+:::{group-tab} Only using GitHub
+### Create an repository on GitHub
+
+First log into GitHub, then follow the screenshots and descriptions below.
+```{figure} img/sharing/new-repository.png
+:alt: Screenshot on GitHub before a new repository form is opened
+:width: 60%
+:class: with-border
+
+Click on the "plus" symbol on top right, then on "New repository".
+```
+
+Then:
+```{figure} img/sharing/create-repository-with-readme.png
+:alt: Screenshot on GitHub just before a new repository is created
+:width: 100%
+:class: with-border
+
+Choose a repository name, add a short description, and in this case **make sure to check** "Add a
+README file". Finally "Create repository".
+```
+
+
+### Upload your files
+
+Now that the repository is created, you can upload your files:
+```{figure} img/sharing/upload-files.png
+:alt: Screenshot on GitHub just before uploading files
+:width: 100%
+:class: with-border
+
+Click on the "+" symbol and then on "Upload files".
+```
+:::
+
+:::{group-tab} VS Code
+In VS Code it only takes few clicks.
+
+First, open the folder in VS Code. My example project here contains two files.
+Then click on the source control icon:
+```{figure} img/sharing/vscode-start.png
+:alt: Screenshot of VS Code before clicking on the source control icon
+:width: 100%
+:class: with-border
+
+Open the folder in VS Code. Then click on the source control icon.
+```
+
+```{figure} img/sharing/vscode-publish-to-github1.png
+:alt: Screenshot of VS Code before publishing to GitHub
+:width: 100%
+:class: with-border
+
+Choose "Publish to GitHub". In this case I never even clicked on "Initialize Repository".
+```
+
+```{figure} img/sharing/vscode-publish-to-github2.png
+:alt: Screenshot of VS Code before publishing to GitHub
+:width: 100%
+:class: with-border
+
+In my case I chose to "Publish to GitHub public repository". Here you can also rename
+the repository if needed.
+```
+
+```{figure} img/sharing/vscode-authorize.png
+:alt: Screenshot of VS Code asking for authorization
+:width: 50%
+:class: with-border
+
+First time you do this you might need to authorize VS Code to access your
+GitHub account by redirecting you to https://github.com/login/oauth/authorize.
+```
+
+```{figure} img/sharing/vscode-publish-to-github3.png
+:alt: Screenshot of VS Code after publishing to GitHub
+:width: 100%
+:class: with-border
+
+After it is published, click on "Open on GitHub".
+```
+:::
+
+:::{group-tab} Command line
+### Put your project under version control
+
+My example project here consists of two files. Replace this with your own
+example files:
+```console
+$ ls -l
+
+.rw------- 19k user 7 Mar 17:36 LICENSE
+.rw------- 21 user 7 Mar 17:36 myscript.py
+```
+
+I will first initialize a Git repository in this directory.
+If you get an error, try without the `-b main` (and your default branch will
+then be called `master`, this will happen for Git versions older than
+2.28):
+```console
+$ git init -b main
+```
+
+Now add and commit the two files to the Git repository:
+```console
+$ git add LICENSE myscript.py
+$ git commit -m "putting my project under version control"
+```
+
+If you want to add all files in one go, you can use `git
+add .` instead of `git add LICENSE myscript.py`.
+
+Now you have a Git repository with one commit. Verify this with `git log`.
+But it's still only on your computer. Let's put it on GitHub next.
+
+
+### Create an empty repository on GitHub
+
+First log into GitHub, then follow the screenshots and descriptions below.
+```{figure} img/sharing/new-repository.png
+:alt: Screenshot on GitHub before a new repository form is opened
+:width: 60%
+:class: with-border
+
+Click on the "plus" symbol on top right, then on "New repository".
+```
+
+Then create an empty repository without any files and without any commits:
+```{figure} img/sharing/create-repository.png
+:alt: Screenshot on GitHub just before a new repository is created
+:width: 100%
+:class: with-border
+
+Choose a repository name, add a short description, but please **do not check** "Add a
+README file". For "Add .gitignore" and "Choose a license" also leave as "None". Finally "Create repository".
+```
+
+Once you click the green "Create repository", you will see a page similar to:
+```{figure} img/sharing/bare-repository.png
+:alt: Screenshot on GitHub after a bare repository was created
+:width: 100%
+:class: with-border
+```
+
+What this means is that we have now an empty project with either an HTTPS or an
+SSH address: click on the HTTPS and SSH buttons to see what happens.
+
+
+### Push an existing repository from your computer to GitHub
+
+We now want to follow the "**... or push an existing repository from the command line**":
+
+1. In your terminal make sure you are still in your myproject directory.
+2. Copy paste the three lines below the red arrow to the terminal and execute
+ those, in my case (**you need to replace the "USER" part and possibly also
+ the repository name**):
+
+`````{tabs}
+ ````{group-tab} SSH
+ ```console
+ $ git remote add origin git@github.com:USER/myproject.git
+ ```
+ ````
+ ````{group-tab} HTTPS
+ ```console
+ $ git remote add origin https://github.com/USER/myproject.git
+ ```
+ ````
+`````
+
+Then:
+```console
+$ git branch -M main
+$ git push -u origin main
+```
+
+The meaning of the above lines:
+- Add a remote reference with the name "origin"
+- Rename current branch to "main"
+- Push branch "main" to "origin"
+
+You should now see:
+
+```text
+Enumerating objects: 4, done.
+Counting objects: 100% (4/4), done.
+Delta compression using up to 12 threads
+Compressing objects: 100% (3/3), done.
+Writing objects: 100% (4/4), 6.08 KiB | 6.08 MiB/s, done.
+Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
+To github.com:USER/myproject.git
+ * [new branch] main -> main
+branch 'main' set up to track 'origin/main'.
+```
+
+Reload your GitHub project website and your commits should now be
+online!
+
+
+### Troubleshooting
+
+**error: remote origin already exists**
+- Explanation: You probably ran a `git remote add origin ...` command, then changed your
+ mind about HTTPS or SSH and then tried to run the other `git remote add
+ origin ...` command but "origin" then already exists.
+- Recovery:
+ - First remove "origin" with `git remote remove origin`
+ - Then run the correct `git remote add origin ...` command
+
+**remote contains work that you do not have**
+- Explanation: You probably clicked on "Add a README file" and now the
+ repository on GitHub is not empty but contains one commit and locally you
+ have a different history. Git now prevents you from accidentally overwriting
+ the history on GitHub.
+- Recovery:
+ - Use `git push --force` instead of `git push`, which will force Git to overwrite the history on GitHub
+ - Note that this is a powerful but also possibly dangerous option but here it
+ helps us. If it's a brand new repo, it probably is fine to do this. For real
+ repositories, don't do this unless you are very sure what is happening.
+:::
+
+:::{group-tab} RStudio
+
+This is not fully explained, because a lot of it is similar to the "Command
+line" method (and an RStudio expert could help us some). The main differences
+are:
+
+### Put your project under version control
+
+1. Tools → Version control → Project setup → Version control system = Git.
+1. Select "Yes" for "do you want to initialize a new git repository for this project.
+1. Select yes to restart the project with RStudio.
+1. Switch to branch `main` to have you branch named that.
+
+### Create an empty repository on GitHub
+
+Same as command line
+
+### Push an existing repository from your computer to GitHub
+
+1. Under the "Create new branch" button → "Add Remote"
+1. Remote name: `origin`
+1. Remote URL: as in command line (remember to select SSH or HTTPS as you have configured your RStudio)
+1. The "Push" (up arrow) button will send changes on your current branch to the remote. The "Pull" (down arrow) will get changes from the remote.
+
+### Troubleshooting
+
+Same as command line
+
+:::
+::::
+:::::
+
+
+## Is putting software on GitHub/GitLab/... publishing?
+
+It is a good first step but to make your code truly **findable and
+accessible**, consider making your code **citable and persistent**: Get a
+persistent identifier (PID) such as DOI in addition to sharing the code
+publicly, by using services like [Zenodo](https://zenodo.org) or similar
+services.
+
+More about this in {doc}`../publishing`.
diff --git a/branch/main/_static/_sphinx_javascript_frameworks_compat.js b/branch/main/_static/_sphinx_javascript_frameworks_compat.js
new file mode 100644
index 0000000..8141580
--- /dev/null
+++ b/branch/main/_static/_sphinx_javascript_frameworks_compat.js
@@ -0,0 +1,123 @@
+/* Compatability shim for jQuery and underscores.js.
+ *
+ * Copyright Sphinx contributors
+ * Released under the two clause BSD licence
+ */
+
+/**
+ * small helper function to urldecode strings
+ *
+ * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL
+ */
+jQuery.urldecode = function(x) {
+ if (!x) {
+ return x
+ }
+ return decodeURIComponent(x.replace(/\+/g, ' '));
+};
+
+/**
+ * small helper function to urlencode strings
+ */
+jQuery.urlencode = encodeURIComponent;
+
+/**
+ * This function returns the parsed url parameters of the
+ * current request. Multiple values per key are supported,
+ * it will always return arrays of strings for the value parts.
+ */
+jQuery.getQueryParameters = function(s) {
+ if (typeof s === 'undefined')
+ s = document.location.search;
+ var parts = s.substr(s.indexOf('?') + 1).split('&');
+ var result = {};
+ for (var i = 0; i < parts.length; i++) {
+ var tmp = parts[i].split('=', 2);
+ var key = jQuery.urldecode(tmp[0]);
+ var value = jQuery.urldecode(tmp[1]);
+ if (key in result)
+ result[key].push(value);
+ else
+ result[key] = [value];
+ }
+ return result;
+};
+
+/**
+ * highlight a given string on a jquery object by wrapping it in
+ * span elements with the given class name.
+ */
+jQuery.fn.highlightText = function(text, className) {
+ function highlight(node, addItems) {
+ if (node.nodeType === 3) {
+ var val = node.nodeValue;
+ var pos = val.toLowerCase().indexOf(text);
+ if (pos >= 0 &&
+ !jQuery(node.parentNode).hasClass(className) &&
+ !jQuery(node.parentNode).hasClass("nohighlight")) {
+ var span;
+ var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg");
+ if (isInSVG) {
+ span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
+ } else {
+ span = document.createElement("span");
+ span.className = className;
+ }
+ span.appendChild(document.createTextNode(val.substr(pos, text.length)));
+ node.parentNode.insertBefore(span, node.parentNode.insertBefore(
+ document.createTextNode(val.substr(pos + text.length)),
+ node.nextSibling));
+ node.nodeValue = val.substr(0, pos);
+ if (isInSVG) {
+ var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
+ var bbox = node.parentElement.getBBox();
+ rect.x.baseVal.value = bbox.x;
+ rect.y.baseVal.value = bbox.y;
+ rect.width.baseVal.value = bbox.width;
+ rect.height.baseVal.value = bbox.height;
+ rect.setAttribute('class', className);
+ addItems.push({
+ "parent": node.parentNode,
+ "target": rect});
+ }
+ }
+ }
+ else if (!jQuery(node).is("button, select, textarea")) {
+ jQuery.each(node.childNodes, function() {
+ highlight(this, addItems);
+ });
+ }
+ }
+ var addItems = [];
+ var result = this.each(function() {
+ highlight(this, addItems);
+ });
+ for (var i = 0; i < addItems.length; ++i) {
+ jQuery(addItems[i].parent).before(addItems[i].target);
+ }
+ return result;
+};
+
+/*
+ * backward compatibility for jQuery.browser
+ * This will be supported until firefox bug is fixed.
+ */
+if (!jQuery.browser) {
+ jQuery.uaMatch = function(ua) {
+ ua = ua.toLowerCase();
+
+ var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
+ /(webkit)[ \/]([\w.]+)/.exec(ua) ||
+ /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
+ /(msie) ([\w.]+)/.exec(ua) ||
+ ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
+ [];
+
+ return {
+ browser: match[ 1 ] || "",
+ version: match[ 2 ] || "0"
+ };
+ };
+ jQuery.browser = {};
+ jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true;
+}
diff --git a/branch/main/_static/basic.css b/branch/main/_static/basic.css
new file mode 100644
index 0000000..f316efc
--- /dev/null
+++ b/branch/main/_static/basic.css
@@ -0,0 +1,925 @@
+/*
+ * basic.css
+ * ~~~~~~~~~
+ *
+ * Sphinx stylesheet -- basic theme.
+ *
+ * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+/* -- main layout ----------------------------------------------------------- */
+
+div.clearer {
+ clear: both;
+}
+
+div.section::after {
+ display: block;
+ content: '';
+ clear: left;
+}
+
+/* -- relbar ---------------------------------------------------------------- */
+
+div.related {
+ width: 100%;
+ font-size: 90%;
+}
+
+div.related h3 {
+ display: none;
+}
+
+div.related ul {
+ margin: 0;
+ padding: 0 0 0 10px;
+ list-style: none;
+}
+
+div.related li {
+ display: inline;
+}
+
+div.related li.right {
+ float: right;
+ margin-right: 5px;
+}
+
+/* -- sidebar --------------------------------------------------------------- */
+
+div.sphinxsidebarwrapper {
+ padding: 10px 5px 0 10px;
+}
+
+div.sphinxsidebar {
+ float: left;
+ width: 230px;
+ margin-left: -100%;
+ font-size: 90%;
+ word-wrap: break-word;
+ overflow-wrap : break-word;
+}
+
+div.sphinxsidebar ul {
+ list-style: none;
+}
+
+div.sphinxsidebar ul ul,
+div.sphinxsidebar ul.want-points {
+ margin-left: 20px;
+ list-style: square;
+}
+
+div.sphinxsidebar ul ul {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+div.sphinxsidebar form {
+ margin-top: 10px;
+}
+
+div.sphinxsidebar input {
+ border: 1px solid #98dbcc;
+ font-family: sans-serif;
+ font-size: 1em;
+}
+
+div.sphinxsidebar #searchbox form.search {
+ overflow: hidden;
+}
+
+div.sphinxsidebar #searchbox input[type="text"] {
+ float: left;
+ width: 80%;
+ padding: 0.25em;
+ box-sizing: border-box;
+}
+
+div.sphinxsidebar #searchbox input[type="submit"] {
+ float: left;
+ width: 20%;
+ border-left: none;
+ padding: 0.25em;
+ box-sizing: border-box;
+}
+
+
+img {
+ border: 0;
+ max-width: 100%;
+}
+
+/* -- search page ----------------------------------------------------------- */
+
+ul.search {
+ margin: 10px 0 0 20px;
+ padding: 0;
+}
+
+ul.search li {
+ padding: 5px 0 5px 20px;
+ background-image: url(file.png);
+ background-repeat: no-repeat;
+ background-position: 0 7px;
+}
+
+ul.search li a {
+ font-weight: bold;
+}
+
+ul.search li p.context {
+ color: #888;
+ margin: 2px 0 0 30px;
+ text-align: left;
+}
+
+ul.keywordmatches li.goodmatch a {
+ font-weight: bold;
+}
+
+/* -- index page ------------------------------------------------------------ */
+
+table.contentstable {
+ width: 90%;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table.contentstable p.biglink {
+ line-height: 150%;
+}
+
+a.biglink {
+ font-size: 1.3em;
+}
+
+span.linkdescr {
+ font-style: italic;
+ padding-top: 5px;
+ font-size: 90%;
+}
+
+/* -- general index --------------------------------------------------------- */
+
+table.indextable {
+ width: 100%;
+}
+
+table.indextable td {
+ text-align: left;
+ vertical-align: top;
+}
+
+table.indextable ul {
+ margin-top: 0;
+ margin-bottom: 0;
+ list-style-type: none;
+}
+
+table.indextable > tbody > tr > td > ul {
+ padding-left: 0em;
+}
+
+table.indextable tr.pcap {
+ height: 10px;
+}
+
+table.indextable tr.cap {
+ margin-top: 10px;
+ background-color: #f2f2f2;
+}
+
+img.toggler {
+ margin-right: 3px;
+ margin-top: 3px;
+ cursor: pointer;
+}
+
+div.modindex-jumpbox {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ margin: 1em 0 1em 0;
+ padding: 0.4em;
+}
+
+div.genindex-jumpbox {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ margin: 1em 0 1em 0;
+ padding: 0.4em;
+}
+
+/* -- domain module index --------------------------------------------------- */
+
+table.modindextable td {
+ padding: 2px;
+ border-collapse: collapse;
+}
+
+/* -- general body styles --------------------------------------------------- */
+
+div.body {
+ min-width: 360px;
+ max-width: 800px;
+}
+
+div.body p, div.body dd, div.body li, div.body blockquote {
+ -moz-hyphens: auto;
+ -ms-hyphens: auto;
+ -webkit-hyphens: auto;
+ hyphens: auto;
+}
+
+a.headerlink {
+ visibility: hidden;
+}
+
+a:visited {
+ color: #551A8B;
+}
+
+h1:hover > a.headerlink,
+h2:hover > a.headerlink,
+h3:hover > a.headerlink,
+h4:hover > a.headerlink,
+h5:hover > a.headerlink,
+h6:hover > a.headerlink,
+dt:hover > a.headerlink,
+caption:hover > a.headerlink,
+p.caption:hover > a.headerlink,
+div.code-block-caption:hover > a.headerlink {
+ visibility: visible;
+}
+
+div.body p.caption {
+ text-align: inherit;
+}
+
+div.body td {
+ text-align: left;
+}
+
+.first {
+ margin-top: 0 !important;
+}
+
+p.rubric {
+ margin-top: 30px;
+ font-weight: bold;
+}
+
+img.align-left, figure.align-left, .figure.align-left, object.align-left {
+ clear: left;
+ float: left;
+ margin-right: 1em;
+}
+
+img.align-right, figure.align-right, .figure.align-right, object.align-right {
+ clear: right;
+ float: right;
+ margin-left: 1em;
+}
+
+img.align-center, figure.align-center, .figure.align-center, object.align-center {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+img.align-default, figure.align-default, .figure.align-default {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.align-left {
+ text-align: left;
+}
+
+.align-center {
+ text-align: center;
+}
+
+.align-default {
+ text-align: center;
+}
+
+.align-right {
+ text-align: right;
+}
+
+/* -- sidebars -------------------------------------------------------------- */
+
+div.sidebar,
+aside.sidebar {
+ margin: 0 0 0.5em 1em;
+ border: 1px solid #ddb;
+ padding: 7px;
+ background-color: #ffe;
+ width: 40%;
+ float: right;
+ clear: right;
+ overflow-x: auto;
+}
+
+p.sidebar-title {
+ font-weight: bold;
+}
+
+nav.contents,
+aside.topic,
+div.admonition, div.topic, blockquote {
+ clear: left;
+}
+
+/* -- topics ---------------------------------------------------------------- */
+
+nav.contents,
+aside.topic,
+div.topic {
+ border: 1px solid #ccc;
+ padding: 7px;
+ margin: 10px 0 10px 0;
+}
+
+p.topic-title {
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-top: 10px;
+}
+
+/* -- admonitions ----------------------------------------------------------- */
+
+div.admonition {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ padding: 7px;
+}
+
+div.admonition dt {
+ font-weight: bold;
+}
+
+p.admonition-title {
+ margin: 0px 10px 5px 0px;
+ font-weight: bold;
+}
+
+div.body p.centered {
+ text-align: center;
+ margin-top: 25px;
+}
+
+/* -- content of sidebars/topics/admonitions -------------------------------- */
+
+div.sidebar > :last-child,
+aside.sidebar > :last-child,
+nav.contents > :last-child,
+aside.topic > :last-child,
+div.topic > :last-child,
+div.admonition > :last-child {
+ margin-bottom: 0;
+}
+
+div.sidebar::after,
+aside.sidebar::after,
+nav.contents::after,
+aside.topic::after,
+div.topic::after,
+div.admonition::after,
+blockquote::after {
+ display: block;
+ content: '';
+ clear: both;
+}
+
+/* -- tables ---------------------------------------------------------------- */
+
+table.docutils {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ border: 0;
+ border-collapse: collapse;
+}
+
+table.align-center {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table.align-default {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table caption span.caption-number {
+ font-style: italic;
+}
+
+table caption span.caption-text {
+}
+
+table.docutils td, table.docutils th {
+ padding: 1px 8px 1px 5px;
+ border-top: 0;
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 1px solid #aaa;
+}
+
+th {
+ text-align: left;
+ padding-right: 5px;
+}
+
+table.citation {
+ border-left: solid 1px gray;
+ margin-left: 1px;
+}
+
+table.citation td {
+ border-bottom: none;
+}
+
+th > :first-child,
+td > :first-child {
+ margin-top: 0px;
+}
+
+th > :last-child,
+td > :last-child {
+ margin-bottom: 0px;
+}
+
+/* -- figures --------------------------------------------------------------- */
+
+div.figure, figure {
+ margin: 0.5em;
+ padding: 0.5em;
+}
+
+div.figure p.caption, figcaption {
+ padding: 0.3em;
+}
+
+div.figure p.caption span.caption-number,
+figcaption span.caption-number {
+ font-style: italic;
+}
+
+div.figure p.caption span.caption-text,
+figcaption span.caption-text {
+}
+
+/* -- field list styles ----------------------------------------------------- */
+
+table.field-list td, table.field-list th {
+ border: 0 !important;
+}
+
+.field-list ul {
+ margin: 0;
+ padding-left: 1em;
+}
+
+.field-list p {
+ margin: 0;
+}
+
+.field-name {
+ -moz-hyphens: manual;
+ -ms-hyphens: manual;
+ -webkit-hyphens: manual;
+ hyphens: manual;
+}
+
+/* -- hlist styles ---------------------------------------------------------- */
+
+table.hlist {
+ margin: 1em 0;
+}
+
+table.hlist td {
+ vertical-align: top;
+}
+
+/* -- object description styles --------------------------------------------- */
+
+.sig {
+ font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+}
+
+.sig-name, code.descname {
+ background-color: transparent;
+ font-weight: bold;
+}
+
+.sig-name {
+ font-size: 1.1em;
+}
+
+code.descname {
+ font-size: 1.2em;
+}
+
+.sig-prename, code.descclassname {
+ background-color: transparent;
+}
+
+.optional {
+ font-size: 1.3em;
+}
+
+.sig-paren {
+ font-size: larger;
+}
+
+.sig-param.n {
+ font-style: italic;
+}
+
+/* C++ specific styling */
+
+.sig-inline.c-texpr,
+.sig-inline.cpp-texpr {
+ font-family: unset;
+}
+
+.sig.c .k, .sig.c .kt,
+.sig.cpp .k, .sig.cpp .kt {
+ color: #0033B3;
+}
+
+.sig.c .m,
+.sig.cpp .m {
+ color: #1750EB;
+}
+
+.sig.c .s, .sig.c .sc,
+.sig.cpp .s, .sig.cpp .sc {
+ color: #067D17;
+}
+
+
+/* -- other body styles ----------------------------------------------------- */
+
+ol.arabic {
+ list-style: decimal;
+}
+
+ol.loweralpha {
+ list-style: lower-alpha;
+}
+
+ol.upperalpha {
+ list-style: upper-alpha;
+}
+
+ol.lowerroman {
+ list-style: lower-roman;
+}
+
+ol.upperroman {
+ list-style: upper-roman;
+}
+
+:not(li) > ol > li:first-child > :first-child,
+:not(li) > ul > li:first-child > :first-child {
+ margin-top: 0px;
+}
+
+:not(li) > ol > li:last-child > :last-child,
+:not(li) > ul > li:last-child > :last-child {
+ margin-bottom: 0px;
+}
+
+ol.simple ol p,
+ol.simple ul p,
+ul.simple ol p,
+ul.simple ul p {
+ margin-top: 0;
+}
+
+ol.simple > li:not(:first-child) > p,
+ul.simple > li:not(:first-child) > p {
+ margin-top: 0;
+}
+
+ol.simple p,
+ul.simple p {
+ margin-bottom: 0;
+}
+
+aside.footnote > span,
+div.citation > span {
+ float: left;
+}
+aside.footnote > span:last-of-type,
+div.citation > span:last-of-type {
+ padding-right: 0.5em;
+}
+aside.footnote > p {
+ margin-left: 2em;
+}
+div.citation > p {
+ margin-left: 4em;
+}
+aside.footnote > p:last-of-type,
+div.citation > p:last-of-type {
+ margin-bottom: 0em;
+}
+aside.footnote > p:last-of-type:after,
+div.citation > p:last-of-type:after {
+ content: "";
+ clear: both;
+}
+
+dl.field-list {
+ display: grid;
+ grid-template-columns: fit-content(30%) auto;
+}
+
+dl.field-list > dt {
+ font-weight: bold;
+ word-break: break-word;
+ padding-left: 0.5em;
+ padding-right: 5px;
+}
+
+dl.field-list > dd {
+ padding-left: 0.5em;
+ margin-top: 0em;
+ margin-left: 0em;
+ margin-bottom: 0em;
+}
+
+dl {
+ margin-bottom: 15px;
+}
+
+dd > :first-child {
+ margin-top: 0px;
+}
+
+dd ul, dd table {
+ margin-bottom: 10px;
+}
+
+dd {
+ margin-top: 3px;
+ margin-bottom: 10px;
+ margin-left: 30px;
+}
+
+.sig dd {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+.sig dl {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+dl > dd:last-child,
+dl > dd:last-child > :last-child {
+ margin-bottom: 0;
+}
+
+dt:target, span.highlighted {
+ background-color: #fbe54e;
+}
+
+rect.highlighted {
+ fill: #fbe54e;
+}
+
+dl.glossary dt {
+ font-weight: bold;
+ font-size: 1.1em;
+}
+
+.versionmodified {
+ font-style: italic;
+}
+
+.system-message {
+ background-color: #fda;
+ padding: 5px;
+ border: 3px solid red;
+}
+
+.footnote:target {
+ background-color: #ffa;
+}
+
+.line-block {
+ display: block;
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+.line-block .line-block {
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-left: 1.5em;
+}
+
+.guilabel, .menuselection {
+ font-family: sans-serif;
+}
+
+.accelerator {
+ text-decoration: underline;
+}
+
+.classifier {
+ font-style: oblique;
+}
+
+.classifier:before {
+ font-style: normal;
+ margin: 0 0.5em;
+ content: ":";
+ display: inline-block;
+}
+
+abbr, acronym {
+ border-bottom: dotted 1px;
+ cursor: help;
+}
+
+.translated {
+ background-color: rgba(207, 255, 207, 0.2)
+}
+
+.untranslated {
+ background-color: rgba(255, 207, 207, 0.2)
+}
+
+/* -- code displays --------------------------------------------------------- */
+
+pre {
+ overflow: auto;
+ overflow-y: hidden; /* fixes display issues on Chrome browsers */
+}
+
+pre, div[class*="highlight-"] {
+ clear: both;
+}
+
+span.pre {
+ -moz-hyphens: none;
+ -ms-hyphens: none;
+ -webkit-hyphens: none;
+ hyphens: none;
+ white-space: nowrap;
+}
+
+div[class*="highlight-"] {
+ margin: 1em 0;
+}
+
+td.linenos pre {
+ border: 0;
+ background-color: transparent;
+ color: #aaa;
+}
+
+table.highlighttable {
+ display: block;
+}
+
+table.highlighttable tbody {
+ display: block;
+}
+
+table.highlighttable tr {
+ display: flex;
+}
+
+table.highlighttable td {
+ margin: 0;
+ padding: 0;
+}
+
+table.highlighttable td.linenos {
+ padding-right: 0.5em;
+}
+
+table.highlighttable td.code {
+ flex: 1;
+ overflow: hidden;
+}
+
+.highlight .hll {
+ display: block;
+}
+
+div.highlight pre,
+table.highlighttable pre {
+ margin: 0;
+}
+
+div.code-block-caption + div {
+ margin-top: 0;
+}
+
+div.code-block-caption {
+ margin-top: 1em;
+ padding: 2px 5px;
+ font-size: small;
+}
+
+div.code-block-caption code {
+ background-color: transparent;
+}
+
+table.highlighttable td.linenos,
+span.linenos,
+div.highlight span.gp { /* gp: Generic.Prompt */
+ user-select: none;
+ -webkit-user-select: text; /* Safari fallback only */
+ -webkit-user-select: none; /* Chrome/Safari */
+ -moz-user-select: none; /* Firefox */
+ -ms-user-select: none; /* IE10+ */
+}
+
+div.code-block-caption span.caption-number {
+ padding: 0.1em 0.3em;
+ font-style: italic;
+}
+
+div.code-block-caption span.caption-text {
+}
+
+div.literal-block-wrapper {
+ margin: 1em 0;
+}
+
+code.xref, a code {
+ background-color: transparent;
+ font-weight: bold;
+}
+
+h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
+ background-color: transparent;
+}
+
+.viewcode-link {
+ float: right;
+}
+
+.viewcode-back {
+ float: right;
+ font-family: sans-serif;
+}
+
+div.viewcode-block:target {
+ margin: -1px -10px;
+ padding: 0 10px;
+}
+
+/* -- math display ---------------------------------------------------------- */
+
+img.math {
+ vertical-align: middle;
+}
+
+div.body div.math p {
+ text-align: center;
+}
+
+span.eqno {
+ float: right;
+}
+
+span.eqno a.headerlink {
+ position: absolute;
+ z-index: 1;
+}
+
+div.math:hover a.headerlink {
+ visibility: visible;
+}
+
+/* -- printout stylesheet --------------------------------------------------- */
+
+@media print {
+ div.document,
+ div.documentwrapper,
+ div.bodywrapper {
+ margin: 0 !important;
+ width: 100%;
+ }
+
+ div.sphinxsidebar,
+ div.related,
+ div.footer,
+ #top-link {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/branch/main/_static/check-solid.svg b/branch/main/_static/check-solid.svg
new file mode 100644
index 0000000..92fad4b
--- /dev/null
+++ b/branch/main/_static/check-solid.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/branch/main/_static/clipboard.min.js b/branch/main/_static/clipboard.min.js
new file mode 100644
index 0000000..54b3c46
--- /dev/null
+++ b/branch/main/_static/clipboard.min.js
@@ -0,0 +1,7 @@
+/*!
+ * clipboard.js v2.0.8
+ * https://clipboardjs.com/
+ *
+ * Licensed MIT © Zeno Rocha
+ */
+!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1
+
+
+
+
diff --git a/branch/main/_static/copybutton.css b/branch/main/_static/copybutton.css
new file mode 100644
index 0000000..f1916ec
--- /dev/null
+++ b/branch/main/_static/copybutton.css
@@ -0,0 +1,94 @@
+/* Copy buttons */
+button.copybtn {
+ position: absolute;
+ display: flex;
+ top: .3em;
+ right: .3em;
+ width: 1.7em;
+ height: 1.7em;
+ opacity: 0;
+ transition: opacity 0.3s, border .3s, background-color .3s;
+ user-select: none;
+ padding: 0;
+ border: none;
+ outline: none;
+ border-radius: 0.4em;
+ /* The colors that GitHub uses */
+ border: #1b1f2426 1px solid;
+ background-color: #f6f8fa;
+ color: #57606a;
+}
+
+button.copybtn.success {
+ border-color: #22863a;
+ color: #22863a;
+}
+
+button.copybtn svg {
+ stroke: currentColor;
+ width: 1.5em;
+ height: 1.5em;
+ padding: 0.1em;
+}
+
+div.highlight {
+ position: relative;
+}
+
+/* Show the copybutton */
+.highlight:hover button.copybtn, button.copybtn.success {
+ opacity: 1;
+}
+
+.highlight button.copybtn:hover {
+ background-color: rgb(235, 235, 235);
+}
+
+.highlight button.copybtn:active {
+ background-color: rgb(187, 187, 187);
+}
+
+/**
+ * A minimal CSS-only tooltip copied from:
+ * https://codepen.io/mildrenben/pen/rVBrpK
+ *
+ * To use, write HTML like the following:
+ *
+ * Short
+ */
+ .o-tooltip--left {
+ position: relative;
+ }
+
+ .o-tooltip--left:after {
+ opacity: 0;
+ visibility: hidden;
+ position: absolute;
+ content: attr(data-tooltip);
+ padding: .2em;
+ font-size: .8em;
+ left: -.2em;
+ background: grey;
+ color: white;
+ white-space: nowrap;
+ z-index: 2;
+ border-radius: 2px;
+ transform: translateX(-102%) translateY(0);
+ transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1);
+}
+
+.o-tooltip--left:hover:after {
+ display: block;
+ opacity: 1;
+ visibility: visible;
+ transform: translateX(-100%) translateY(0);
+ transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1);
+ transition-delay: .5s;
+}
+
+/* By default the copy button shouldn't show up when printing a page */
+@media print {
+ button.copybtn {
+ display: none;
+ }
+}
diff --git a/branch/main/_static/copybutton.js b/branch/main/_static/copybutton.js
new file mode 100644
index 0000000..b398703
--- /dev/null
+++ b/branch/main/_static/copybutton.js
@@ -0,0 +1,248 @@
+// Localization support
+const messages = {
+ 'en': {
+ 'copy': 'Copy',
+ 'copy_to_clipboard': 'Copy to clipboard',
+ 'copy_success': 'Copied!',
+ 'copy_failure': 'Failed to copy',
+ },
+ 'es' : {
+ 'copy': 'Copiar',
+ 'copy_to_clipboard': 'Copiar al portapapeles',
+ 'copy_success': '¡Copiado!',
+ 'copy_failure': 'Error al copiar',
+ },
+ 'de' : {
+ 'copy': 'Kopieren',
+ 'copy_to_clipboard': 'In die Zwischenablage kopieren',
+ 'copy_success': 'Kopiert!',
+ 'copy_failure': 'Fehler beim Kopieren',
+ },
+ 'fr' : {
+ 'copy': 'Copier',
+ 'copy_to_clipboard': 'Copier dans le presse-papier',
+ 'copy_success': 'Copié !',
+ 'copy_failure': 'Échec de la copie',
+ },
+ 'ru': {
+ 'copy': 'Скопировать',
+ 'copy_to_clipboard': 'Скопировать в буфер',
+ 'copy_success': 'Скопировано!',
+ 'copy_failure': 'Не удалось скопировать',
+ },
+ 'zh-CN': {
+ 'copy': '复制',
+ 'copy_to_clipboard': '复制到剪贴板',
+ 'copy_success': '复制成功!',
+ 'copy_failure': '复制失败',
+ },
+ 'it' : {
+ 'copy': 'Copiare',
+ 'copy_to_clipboard': 'Copiato negli appunti',
+ 'copy_success': 'Copiato!',
+ 'copy_failure': 'Errore durante la copia',
+ }
+}
+
+let locale = 'en'
+if( document.documentElement.lang !== undefined
+ && messages[document.documentElement.lang] !== undefined ) {
+ locale = document.documentElement.lang
+}
+
+let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT;
+if (doc_url_root == '#') {
+ doc_url_root = '';
+}
+
+/**
+ * SVG files for our copy buttons
+ */
+let iconCheck = `
+ ${messages[locale]['copy_success']}
+
+
+ `
+
+// If the user specified their own SVG use that, otherwise use the default
+let iconCopy = ``;
+if (!iconCopy) {
+ iconCopy = `
+ ${messages[locale]['copy_to_clipboard']}
+
+
+
+ `
+}
+
+/**
+ * Set up copy/paste for code blocks
+ */
+
+const runWhenDOMLoaded = cb => {
+ if (document.readyState != 'loading') {
+ cb()
+ } else if (document.addEventListener) {
+ document.addEventListener('DOMContentLoaded', cb)
+ } else {
+ document.attachEvent('onreadystatechange', function() {
+ if (document.readyState == 'complete') cb()
+ })
+ }
+}
+
+const codeCellId = index => `codecell${index}`
+
+// Clears selected text since ClipboardJS will select the text when copying
+const clearSelection = () => {
+ if (window.getSelection) {
+ window.getSelection().removeAllRanges()
+ } else if (document.selection) {
+ document.selection.empty()
+ }
+}
+
+// Changes tooltip text for a moment, then changes it back
+// We want the timeout of our `success` class to be a bit shorter than the
+// tooltip and icon change, so that we can hide the icon before changing back.
+var timeoutIcon = 2000;
+var timeoutSuccessClass = 1500;
+
+const temporarilyChangeTooltip = (el, oldText, newText) => {
+ el.setAttribute('data-tooltip', newText)
+ el.classList.add('success')
+ // Remove success a little bit sooner than we change the tooltip
+ // So that we can use CSS to hide the copybutton first
+ setTimeout(() => el.classList.remove('success'), timeoutSuccessClass)
+ setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon)
+}
+
+// Changes the copy button icon for two seconds, then changes it back
+const temporarilyChangeIcon = (el) => {
+ el.innerHTML = iconCheck;
+ setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon)
+}
+
+const addCopyButtonToCodeCells = () => {
+ // If ClipboardJS hasn't loaded, wait a bit and try again. This
+ // happens because we load ClipboardJS asynchronously.
+ if (window.ClipboardJS === undefined) {
+ setTimeout(addCopyButtonToCodeCells, 250)
+ return
+ }
+
+ // Add copybuttons to all of our code cells
+ const COPYBUTTON_SELECTOR = 'div.highlight pre';
+ const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR)
+ codeCells.forEach((codeCell, index) => {
+ const id = codeCellId(index)
+ codeCell.setAttribute('id', id)
+
+ const clipboardButton = id =>
+ `
+ ${iconCopy}
+ `
+ codeCell.insertAdjacentHTML('afterend', clipboardButton(id))
+ })
+
+function escapeRegExp(string) {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
+}
+
+/**
+ * Removes excluded text from a Node.
+ *
+ * @param {Node} target Node to filter.
+ * @param {string} exclude CSS selector of nodes to exclude.
+ * @returns {DOMString} Text from `target` with text removed.
+ */
+function filterText(target, exclude) {
+ const clone = target.cloneNode(true); // clone as to not modify the live DOM
+ if (exclude) {
+ // remove excluded nodes
+ clone.querySelectorAll(exclude).forEach(node => node.remove());
+ }
+ return clone.innerText;
+}
+
+// Callback when a copy button is clicked. Will be passed the node that was clicked
+// should then grab the text and replace pieces of text that shouldn't be used in output
+function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") {
+ var regexp;
+ var match;
+
+ // Do we check for line continuation characters and "HERE-documents"?
+ var useLineCont = !!lineContinuationChar
+ var useHereDoc = !!hereDocDelim
+
+ // create regexp to capture prompt and remaining line
+ if (isRegexp) {
+ regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)')
+ } else {
+ regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)')
+ }
+
+ const outputLines = [];
+ var promptFound = false;
+ var gotLineCont = false;
+ var gotHereDoc = false;
+ const lineGotPrompt = [];
+ for (const line of textContent.split('\n')) {
+ match = line.match(regexp)
+ if (match || gotLineCont || gotHereDoc) {
+ promptFound = regexp.test(line)
+ lineGotPrompt.push(promptFound)
+ if (removePrompts && promptFound) {
+ outputLines.push(match[2])
+ } else {
+ outputLines.push(line)
+ }
+ gotLineCont = line.endsWith(lineContinuationChar) & useLineCont
+ if (line.includes(hereDocDelim) & useHereDoc)
+ gotHereDoc = !gotHereDoc
+ } else if (!onlyCopyPromptLines) {
+ outputLines.push(line)
+ } else if (copyEmptyLines && line.trim() === '') {
+ outputLines.push(line)
+ }
+ }
+
+ // If no lines with the prompt were found then just use original lines
+ if (lineGotPrompt.some(v => v === true)) {
+ textContent = outputLines.join('\n');
+ }
+
+ // Remove a trailing newline to avoid auto-running when pasting
+ if (textContent.endsWith("\n")) {
+ textContent = textContent.slice(0, -1)
+ }
+ return textContent
+}
+
+
+var copyTargetText = (trigger) => {
+ var target = document.querySelector(trigger.attributes['data-clipboard-target'].value);
+
+ // get filtered text
+ let exclude = '.linenos, .gp';
+
+ let text = filterText(target, exclude);
+ return formatCopyText(text, '', false, true, true, true, '', '')
+}
+
+ // Initialize with a callback so we can modify the text before copy
+ const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText})
+
+ // Update UI with error/success messages
+ clipboard.on('success', event => {
+ clearSelection()
+ temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success'])
+ temporarilyChangeIcon(event.trigger)
+ })
+
+ clipboard.on('error', event => {
+ temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure'])
+ })
+}
+
+runWhenDOMLoaded(addCopyButtonToCodeCells)
\ No newline at end of file
diff --git a/branch/main/_static/copybutton_funcs.js b/branch/main/_static/copybutton_funcs.js
new file mode 100644
index 0000000..dbe1aaa
--- /dev/null
+++ b/branch/main/_static/copybutton_funcs.js
@@ -0,0 +1,73 @@
+function escapeRegExp(string) {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
+}
+
+/**
+ * Removes excluded text from a Node.
+ *
+ * @param {Node} target Node to filter.
+ * @param {string} exclude CSS selector of nodes to exclude.
+ * @returns {DOMString} Text from `target` with text removed.
+ */
+export function filterText(target, exclude) {
+ const clone = target.cloneNode(true); // clone as to not modify the live DOM
+ if (exclude) {
+ // remove excluded nodes
+ clone.querySelectorAll(exclude).forEach(node => node.remove());
+ }
+ return clone.innerText;
+}
+
+// Callback when a copy button is clicked. Will be passed the node that was clicked
+// should then grab the text and replace pieces of text that shouldn't be used in output
+export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") {
+ var regexp;
+ var match;
+
+ // Do we check for line continuation characters and "HERE-documents"?
+ var useLineCont = !!lineContinuationChar
+ var useHereDoc = !!hereDocDelim
+
+ // create regexp to capture prompt and remaining line
+ if (isRegexp) {
+ regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)')
+ } else {
+ regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)')
+ }
+
+ const outputLines = [];
+ var promptFound = false;
+ var gotLineCont = false;
+ var gotHereDoc = false;
+ const lineGotPrompt = [];
+ for (const line of textContent.split('\n')) {
+ match = line.match(regexp)
+ if (match || gotLineCont || gotHereDoc) {
+ promptFound = regexp.test(line)
+ lineGotPrompt.push(promptFound)
+ if (removePrompts && promptFound) {
+ outputLines.push(match[2])
+ } else {
+ outputLines.push(line)
+ }
+ gotLineCont = line.endsWith(lineContinuationChar) & useLineCont
+ if (line.includes(hereDocDelim) & useHereDoc)
+ gotHereDoc = !gotHereDoc
+ } else if (!onlyCopyPromptLines) {
+ outputLines.push(line)
+ } else if (copyEmptyLines && line.trim() === '') {
+ outputLines.push(line)
+ }
+ }
+
+ // If no lines with the prompt were found then just use original lines
+ if (lineGotPrompt.some(v => v === true)) {
+ textContent = outputLines.join('\n');
+ }
+
+ // Remove a trailing newline to avoid auto-running when pasting
+ if (textContent.endsWith("\n")) {
+ textContent = textContent.slice(0, -1)
+ }
+ return textContent
+}
diff --git a/branch/main/_static/css/badge_only.css b/branch/main/_static/css/badge_only.css
new file mode 100644
index 0000000..88ba55b
--- /dev/null
+++ b/branch/main/_static/css/badge_only.css
@@ -0,0 +1 @@
+.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}
\ No newline at end of file
diff --git a/branch/main/_static/css/fonts/Roboto-Slab-Bold.woff b/branch/main/_static/css/fonts/Roboto-Slab-Bold.woff
new file mode 100644
index 0000000..6cb6000
Binary files /dev/null and b/branch/main/_static/css/fonts/Roboto-Slab-Bold.woff differ
diff --git a/branch/main/_static/css/fonts/Roboto-Slab-Bold.woff2 b/branch/main/_static/css/fonts/Roboto-Slab-Bold.woff2
new file mode 100644
index 0000000..7059e23
Binary files /dev/null and b/branch/main/_static/css/fonts/Roboto-Slab-Bold.woff2 differ
diff --git a/branch/main/_static/css/fonts/Roboto-Slab-Regular.woff b/branch/main/_static/css/fonts/Roboto-Slab-Regular.woff
new file mode 100644
index 0000000..f815f63
Binary files /dev/null and b/branch/main/_static/css/fonts/Roboto-Slab-Regular.woff differ
diff --git a/branch/main/_static/css/fonts/Roboto-Slab-Regular.woff2 b/branch/main/_static/css/fonts/Roboto-Slab-Regular.woff2
new file mode 100644
index 0000000..f2c76e5
Binary files /dev/null and b/branch/main/_static/css/fonts/Roboto-Slab-Regular.woff2 differ
diff --git a/branch/main/_static/css/fonts/fontawesome-webfont.eot b/branch/main/_static/css/fonts/fontawesome-webfont.eot
new file mode 100644
index 0000000..e9f60ca
Binary files /dev/null and b/branch/main/_static/css/fonts/fontawesome-webfont.eot differ
diff --git a/branch/main/_static/css/fonts/fontawesome-webfont.svg b/branch/main/_static/css/fonts/fontawesome-webfont.svg
new file mode 100644
index 0000000..855c845
--- /dev/null
+++ b/branch/main/_static/css/fonts/fontawesome-webfont.svg
@@ -0,0 +1,2671 @@
+
+
+
+
+Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016
+ By ,,,
+Copyright Dave Gandy 2016. All rights reserved.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/branch/main/_static/css/fonts/fontawesome-webfont.ttf b/branch/main/_static/css/fonts/fontawesome-webfont.ttf
new file mode 100644
index 0000000..35acda2
Binary files /dev/null and b/branch/main/_static/css/fonts/fontawesome-webfont.ttf differ
diff --git a/branch/main/_static/css/fonts/fontawesome-webfont.woff b/branch/main/_static/css/fonts/fontawesome-webfont.woff
new file mode 100644
index 0000000..400014a
Binary files /dev/null and b/branch/main/_static/css/fonts/fontawesome-webfont.woff differ
diff --git a/branch/main/_static/css/fonts/fontawesome-webfont.woff2 b/branch/main/_static/css/fonts/fontawesome-webfont.woff2
new file mode 100644
index 0000000..4d13fc6
Binary files /dev/null and b/branch/main/_static/css/fonts/fontawesome-webfont.woff2 differ
diff --git a/branch/main/_static/css/fonts/lato-bold-italic.woff b/branch/main/_static/css/fonts/lato-bold-italic.woff
new file mode 100644
index 0000000..88ad05b
Binary files /dev/null and b/branch/main/_static/css/fonts/lato-bold-italic.woff differ
diff --git a/branch/main/_static/css/fonts/lato-bold-italic.woff2 b/branch/main/_static/css/fonts/lato-bold-italic.woff2
new file mode 100644
index 0000000..c4e3d80
Binary files /dev/null and b/branch/main/_static/css/fonts/lato-bold-italic.woff2 differ
diff --git a/branch/main/_static/css/fonts/lato-bold.woff b/branch/main/_static/css/fonts/lato-bold.woff
new file mode 100644
index 0000000..c6dff51
Binary files /dev/null and b/branch/main/_static/css/fonts/lato-bold.woff differ
diff --git a/branch/main/_static/css/fonts/lato-bold.woff2 b/branch/main/_static/css/fonts/lato-bold.woff2
new file mode 100644
index 0000000..bb19504
Binary files /dev/null and b/branch/main/_static/css/fonts/lato-bold.woff2 differ
diff --git a/branch/main/_static/css/fonts/lato-normal-italic.woff b/branch/main/_static/css/fonts/lato-normal-italic.woff
new file mode 100644
index 0000000..76114bc
Binary files /dev/null and b/branch/main/_static/css/fonts/lato-normal-italic.woff differ
diff --git a/branch/main/_static/css/fonts/lato-normal-italic.woff2 b/branch/main/_static/css/fonts/lato-normal-italic.woff2
new file mode 100644
index 0000000..3404f37
Binary files /dev/null and b/branch/main/_static/css/fonts/lato-normal-italic.woff2 differ
diff --git a/branch/main/_static/css/fonts/lato-normal.woff b/branch/main/_static/css/fonts/lato-normal.woff
new file mode 100644
index 0000000..ae1307f
Binary files /dev/null and b/branch/main/_static/css/fonts/lato-normal.woff differ
diff --git a/branch/main/_static/css/fonts/lato-normal.woff2 b/branch/main/_static/css/fonts/lato-normal.woff2
new file mode 100644
index 0000000..3bf9843
Binary files /dev/null and b/branch/main/_static/css/fonts/lato-normal.woff2 differ
diff --git a/branch/main/_static/css/theme.css b/branch/main/_static/css/theme.css
new file mode 100644
index 0000000..0f14f10
--- /dev/null
+++ b/branch/main/_static/css/theme.css
@@ -0,0 +1,4 @@
+html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*!
+ * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
+ * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search .wy-dropdown>aactive,.wy-side-nav-search .wy-dropdown>afocus,.wy-side-nav-search>a:hover,.wy-side-nav-search>aactive,.wy-side-nav-search>afocus{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon,.wy-side-nav-search>a.icon{display:block}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.switch-menus{position:relative;display:block;margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-side-nav-search>div.switch-menus>div.language-switch,.wy-side-nav-search>div.switch-menus>div.version-switch{display:inline-block;padding:.2em}.wy-side-nav-search>div.switch-menus>div.language-switch select,.wy-side-nav-search>div.switch-menus>div.version-switch select{display:inline-block;margin-right:-2rem;padding-right:2rem;max-width:240px;text-align-last:center;background:none;border:none;border-radius:0;box-shadow:none;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-size:1em;font-weight:400;color:hsla(0,0%,100%,.3);cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none}.wy-side-nav-search>div.switch-menus>div.language-switch select:active,.wy-side-nav-search>div.switch-menus>div.language-switch select:focus,.wy-side-nav-search>div.switch-menus>div.language-switch select:hover,.wy-side-nav-search>div.switch-menus>div.version-switch select:active,.wy-side-nav-search>div.switch-menus>div.version-switch select:focus,.wy-side-nav-search>div.switch-menus>div.version-switch select:hover{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.wy-side-nav-search>div.switch-menus>div.language-switch select option,.wy-side-nav-search>div.switch-menus>div.version-switch select option{color:#000}.wy-side-nav-search>div.switch-menus>div.language-switch:has(>select):after,.wy-side-nav-search>div.switch-menus>div.version-switch:has(>select):after{display:inline-block;width:1.5em;height:100%;padding:.1em;content:"\f0d7";font-size:1em;line-height:1.2em;font-family:FontAwesome;text-align:center;pointer-events:none;box-sizing:border-box}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block}
\ No newline at end of file
diff --git a/branch/main/_static/doctools.js b/branch/main/_static/doctools.js
new file mode 100644
index 0000000..4d67807
--- /dev/null
+++ b/branch/main/_static/doctools.js
@@ -0,0 +1,156 @@
+/*
+ * doctools.js
+ * ~~~~~~~~~~~
+ *
+ * Base JavaScript utilities for all Sphinx HTML documentation.
+ *
+ * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+"use strict";
+
+const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([
+ "TEXTAREA",
+ "INPUT",
+ "SELECT",
+ "BUTTON",
+]);
+
+const _ready = (callback) => {
+ if (document.readyState !== "loading") {
+ callback();
+ } else {
+ document.addEventListener("DOMContentLoaded", callback);
+ }
+};
+
+/**
+ * Small JavaScript module for the documentation.
+ */
+const Documentation = {
+ init: () => {
+ Documentation.initDomainIndexTable();
+ Documentation.initOnKeyListeners();
+ },
+
+ /**
+ * i18n support
+ */
+ TRANSLATIONS: {},
+ PLURAL_EXPR: (n) => (n === 1 ? 0 : 1),
+ LOCALE: "unknown",
+
+ // gettext and ngettext don't access this so that the functions
+ // can safely bound to a different name (_ = Documentation.gettext)
+ gettext: (string) => {
+ const translated = Documentation.TRANSLATIONS[string];
+ switch (typeof translated) {
+ case "undefined":
+ return string; // no translation
+ case "string":
+ return translated; // translation exists
+ default:
+ return translated[0]; // (singular, plural) translation tuple exists
+ }
+ },
+
+ ngettext: (singular, plural, n) => {
+ const translated = Documentation.TRANSLATIONS[singular];
+ if (typeof translated !== "undefined")
+ return translated[Documentation.PLURAL_EXPR(n)];
+ return n === 1 ? singular : plural;
+ },
+
+ addTranslations: (catalog) => {
+ Object.assign(Documentation.TRANSLATIONS, catalog.messages);
+ Documentation.PLURAL_EXPR = new Function(
+ "n",
+ `return (${catalog.plural_expr})`
+ );
+ Documentation.LOCALE = catalog.locale;
+ },
+
+ /**
+ * helper function to focus on search bar
+ */
+ focusSearchBar: () => {
+ document.querySelectorAll("input[name=q]")[0]?.focus();
+ },
+
+ /**
+ * Initialise the domain index toggle buttons
+ */
+ initDomainIndexTable: () => {
+ const toggler = (el) => {
+ const idNumber = el.id.substr(7);
+ const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`);
+ if (el.src.substr(-9) === "minus.png") {
+ el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`;
+ toggledRows.forEach((el) => (el.style.display = "none"));
+ } else {
+ el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`;
+ toggledRows.forEach((el) => (el.style.display = ""));
+ }
+ };
+
+ const togglerElements = document.querySelectorAll("img.toggler");
+ togglerElements.forEach((el) =>
+ el.addEventListener("click", (event) => toggler(event.currentTarget))
+ );
+ togglerElements.forEach((el) => (el.style.display = ""));
+ if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler);
+ },
+
+ initOnKeyListeners: () => {
+ // only install a listener if it is really needed
+ if (
+ !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS &&
+ !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS
+ )
+ return;
+
+ document.addEventListener("keydown", (event) => {
+ // bail for input elements
+ if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
+ // bail with special keys
+ if (event.altKey || event.ctrlKey || event.metaKey) return;
+
+ if (!event.shiftKey) {
+ switch (event.key) {
+ case "ArrowLeft":
+ if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
+
+ const prevLink = document.querySelector('link[rel="prev"]');
+ if (prevLink && prevLink.href) {
+ window.location.href = prevLink.href;
+ event.preventDefault();
+ }
+ break;
+ case "ArrowRight":
+ if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
+
+ const nextLink = document.querySelector('link[rel="next"]');
+ if (nextLink && nextLink.href) {
+ window.location.href = nextLink.href;
+ event.preventDefault();
+ }
+ break;
+ }
+ }
+
+ // some keyboard layouts may need Shift to get /
+ switch (event.key) {
+ case "/":
+ if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break;
+ Documentation.focusSearchBar();
+ event.preventDefault();
+ }
+ });
+ },
+};
+
+// quick alias for translations
+const _ = Documentation.gettext;
+
+_ready(Documentation.init);
diff --git a/branch/main/_static/documentation_options.js b/branch/main/_static/documentation_options.js
new file mode 100644
index 0000000..87fc516
--- /dev/null
+++ b/branch/main/_static/documentation_options.js
@@ -0,0 +1,13 @@
+const DOCUMENTATION_OPTIONS = {
+ VERSION: '',
+ LANGUAGE: 'en',
+ COLLAPSE_INDEX: false,
+ BUILDER: 'dirhtml',
+ FILE_SUFFIX: '.html',
+ LINK_SUFFIX: '.html',
+ HAS_SOURCE: true,
+ SOURCELINK_SUFFIX: '.txt',
+ NAVIGATION_WITH_KEYS: false,
+ SHOW_SEARCH_SUMMARY: true,
+ ENABLE_SEARCH_SHORTCUTS: true,
+};
\ No newline at end of file
diff --git a/branch/main/_static/file.png b/branch/main/_static/file.png
new file mode 100644
index 0000000..a858a41
Binary files /dev/null and b/branch/main/_static/file.png differ
diff --git a/branch/main/_static/fonts/Lato/lato-bold.eot b/branch/main/_static/fonts/Lato/lato-bold.eot
new file mode 100644
index 0000000..3361183
Binary files /dev/null and b/branch/main/_static/fonts/Lato/lato-bold.eot differ
diff --git a/branch/main/_static/fonts/Lato/lato-bold.ttf b/branch/main/_static/fonts/Lato/lato-bold.ttf
new file mode 100644
index 0000000..29f691d
Binary files /dev/null and b/branch/main/_static/fonts/Lato/lato-bold.ttf differ
diff --git a/branch/main/_static/fonts/Lato/lato-bold.woff b/branch/main/_static/fonts/Lato/lato-bold.woff
new file mode 100644
index 0000000..c6dff51
Binary files /dev/null and b/branch/main/_static/fonts/Lato/lato-bold.woff differ
diff --git a/branch/main/_static/fonts/Lato/lato-bold.woff2 b/branch/main/_static/fonts/Lato/lato-bold.woff2
new file mode 100644
index 0000000..bb19504
Binary files /dev/null and b/branch/main/_static/fonts/Lato/lato-bold.woff2 differ
diff --git a/branch/main/_static/fonts/Lato/lato-bolditalic.eot b/branch/main/_static/fonts/Lato/lato-bolditalic.eot
new file mode 100644
index 0000000..3d41549
Binary files /dev/null and b/branch/main/_static/fonts/Lato/lato-bolditalic.eot differ
diff --git a/branch/main/_static/fonts/Lato/lato-bolditalic.ttf b/branch/main/_static/fonts/Lato/lato-bolditalic.ttf
new file mode 100644
index 0000000..f402040
Binary files /dev/null and b/branch/main/_static/fonts/Lato/lato-bolditalic.ttf differ
diff --git a/branch/main/_static/fonts/Lato/lato-bolditalic.woff b/branch/main/_static/fonts/Lato/lato-bolditalic.woff
new file mode 100644
index 0000000..88ad05b
Binary files /dev/null and b/branch/main/_static/fonts/Lato/lato-bolditalic.woff differ
diff --git a/branch/main/_static/fonts/Lato/lato-bolditalic.woff2 b/branch/main/_static/fonts/Lato/lato-bolditalic.woff2
new file mode 100644
index 0000000..c4e3d80
Binary files /dev/null and b/branch/main/_static/fonts/Lato/lato-bolditalic.woff2 differ
diff --git a/branch/main/_static/fonts/Lato/lato-italic.eot b/branch/main/_static/fonts/Lato/lato-italic.eot
new file mode 100644
index 0000000..3f82642
Binary files /dev/null and b/branch/main/_static/fonts/Lato/lato-italic.eot differ
diff --git a/branch/main/_static/fonts/Lato/lato-italic.ttf b/branch/main/_static/fonts/Lato/lato-italic.ttf
new file mode 100644
index 0000000..b4bfc9b
Binary files /dev/null and b/branch/main/_static/fonts/Lato/lato-italic.ttf differ
diff --git a/branch/main/_static/fonts/Lato/lato-italic.woff b/branch/main/_static/fonts/Lato/lato-italic.woff
new file mode 100644
index 0000000..76114bc
Binary files /dev/null and b/branch/main/_static/fonts/Lato/lato-italic.woff differ
diff --git a/branch/main/_static/fonts/Lato/lato-italic.woff2 b/branch/main/_static/fonts/Lato/lato-italic.woff2
new file mode 100644
index 0000000..3404f37
Binary files /dev/null and b/branch/main/_static/fonts/Lato/lato-italic.woff2 differ
diff --git a/branch/main/_static/fonts/Lato/lato-regular.eot b/branch/main/_static/fonts/Lato/lato-regular.eot
new file mode 100644
index 0000000..11e3f2a
Binary files /dev/null and b/branch/main/_static/fonts/Lato/lato-regular.eot differ
diff --git a/branch/main/_static/fonts/Lato/lato-regular.ttf b/branch/main/_static/fonts/Lato/lato-regular.ttf
new file mode 100644
index 0000000..74decd9
Binary files /dev/null and b/branch/main/_static/fonts/Lato/lato-regular.ttf differ
diff --git a/branch/main/_static/fonts/Lato/lato-regular.woff b/branch/main/_static/fonts/Lato/lato-regular.woff
new file mode 100644
index 0000000..ae1307f
Binary files /dev/null and b/branch/main/_static/fonts/Lato/lato-regular.woff differ
diff --git a/branch/main/_static/fonts/Lato/lato-regular.woff2 b/branch/main/_static/fonts/Lato/lato-regular.woff2
new file mode 100644
index 0000000..3bf9843
Binary files /dev/null and b/branch/main/_static/fonts/Lato/lato-regular.woff2 differ
diff --git a/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot
new file mode 100644
index 0000000..79dc8ef
Binary files /dev/null and b/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot differ
diff --git a/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf
new file mode 100644
index 0000000..df5d1df
Binary files /dev/null and b/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf differ
diff --git a/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff
new file mode 100644
index 0000000..6cb6000
Binary files /dev/null and b/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff differ
diff --git a/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2
new file mode 100644
index 0000000..7059e23
Binary files /dev/null and b/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 differ
diff --git a/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot
new file mode 100644
index 0000000..2f7ca78
Binary files /dev/null and b/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot differ
diff --git a/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf
new file mode 100644
index 0000000..eb52a79
Binary files /dev/null and b/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf differ
diff --git a/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff
new file mode 100644
index 0000000..f815f63
Binary files /dev/null and b/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff differ
diff --git a/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2
new file mode 100644
index 0000000..f2c76e5
Binary files /dev/null and b/branch/main/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 differ
diff --git a/branch/main/_static/jquery.js b/branch/main/_static/jquery.js
new file mode 100644
index 0000000..c4c6022
--- /dev/null
+++ b/branch/main/_static/jquery.js
@@ -0,0 +1,2 @@
+/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */
+!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML=" ",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML=" ";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML=" ",y.option=!!ce.lastChild;var ge={thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 "),n("table.docutils.footnote").wrap(""),n("table.docutils.citation").wrap(" a.language.name.localeCompare(b.language.name));
+
+ const languagesHTML = `
+
+ Languages
+ ${languages
+ .map(
+ (translation) => `
+
+ ${translation.language.code}
+
+ `,
+ )
+ .join("\n")}
+
+ `;
+ return languagesHTML;
+ }
+
+ function renderVersions(config) {
+ if (!config.versions.active.length) {
+ return "";
+ }
+ const versionsHTML = `
+
+ Versions
+ ${config.versions.active
+ .map(
+ (version) => `
+
+ ${version.slug}
+
+ `,
+ )
+ .join("\n")}
+
+ `;
+ return versionsHTML;
+ }
+
+ function renderDownloads(config) {
+ if (!Object.keys(config.versions.current.downloads).length) {
+ return "";
+ }
+ const downloadsNameDisplay = {
+ pdf: "PDF",
+ epub: "Epub",
+ htmlzip: "HTML",
+ };
+
+ const downloadsHTML = `
+
+ Downloads
+ ${Object.entries(config.versions.current.downloads)
+ .map(
+ ([name, url]) => `
+
+ ${downloadsNameDisplay[name]}
+
+ `,
+ )
+ .join("\n")}
+
+ `;
+ return downloadsHTML;
+ }
+
+ document.addEventListener("readthedocs-addons-data-ready", function (event) {
+ const config = event.detail.data();
+
+ const flyout = `
+
+
+ Read the Docs
+ v: ${config.versions.current.slug}
+
+
+
+
+ ${renderLanguages(config)}
+ ${renderVersions(config)}
+ ${renderDownloads(config)}
+
+ On Read the Docs
+
+ Project Home
+
+
+ Builds
+
+
+ Downloads
+
+
+
+ Search
+
+
+
+
+
+
+ Hosted by Read the Docs
+
+
+
+ `;
+
+ // Inject the generated flyout into the body HTML element.
+ document.body.insertAdjacentHTML("beforeend", flyout);
+
+ // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout.
+ document
+ .querySelector("#flyout-search-form")
+ .addEventListener("focusin", () => {
+ const event = new CustomEvent("readthedocs-search-show");
+ document.dispatchEvent(event);
+ });
+ })
+}
+
+if (themeLanguageSelector || themeVersionSelector) {
+ function onSelectorSwitch(event) {
+ const option = event.target.selectedIndex;
+ const item = event.target.options[option];
+ window.location.href = item.dataset.url;
+ }
+
+ document.addEventListener("readthedocs-addons-data-ready", function (event) {
+ const config = event.detail.data();
+
+ const versionSwitch = document.querySelector(
+ "div.switch-menus > div.version-switch",
+ );
+ if (themeVersionSelector) {
+ let versions = config.versions.active;
+ if (config.versions.current.hidden || config.versions.current.type === "external") {
+ versions.unshift(config.versions.current);
+ }
+ const versionSelect = `
+
+ ${versions
+ .map(
+ (version) => `
+
+ ${version.slug}
+ `,
+ )
+ .join("\n")}
+
+ `;
+
+ versionSwitch.innerHTML = versionSelect;
+ versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch);
+ }
+
+ const languageSwitch = document.querySelector(
+ "div.switch-menus > div.language-switch",
+ );
+
+ if (themeLanguageSelector) {
+ if (config.projects.translations.length) {
+ // Add the current language to the options on the selector
+ let languages = config.projects.translations.concat(
+ config.projects.current,
+ );
+ languages = languages.sort((a, b) =>
+ a.language.name.localeCompare(b.language.name),
+ );
+
+ const languageSelect = `
+
+ ${languages
+ .map(
+ (language) => `
+
+ ${language.language.name}
+ `,
+ )
+ .join("\n")}
+
+ `;
+
+ languageSwitch.innerHTML = languageSelect;
+ languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch);
+ }
+ else {
+ languageSwitch.remove();
+ }
+ }
+ });
+}
+
+document.addEventListener("readthedocs-addons-data-ready", function (event) {
+ // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav.
+ document
+ .querySelector("[role='search'] input")
+ .addEventListener("focusin", () => {
+ const event = new CustomEvent("readthedocs-search-show");
+ document.dispatchEvent(event);
+ });
+});
\ No newline at end of file
diff --git a/branch/main/_static/language_data.js b/branch/main/_static/language_data.js
new file mode 100644
index 0000000..367b8ed
--- /dev/null
+++ b/branch/main/_static/language_data.js
@@ -0,0 +1,199 @@
+/*
+ * language_data.js
+ * ~~~~~~~~~~~~~~~~
+ *
+ * This script contains the language-specific data used by searchtools.js,
+ * namely the list of stopwords, stemmer, scorer and splitter.
+ *
+ * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"];
+
+
+/* Non-minified version is copied as a separate JS file, if available */
+
+/**
+ * Porter Stemmer
+ */
+var Stemmer = function() {
+
+ var step2list = {
+ ational: 'ate',
+ tional: 'tion',
+ enci: 'ence',
+ anci: 'ance',
+ izer: 'ize',
+ bli: 'ble',
+ alli: 'al',
+ entli: 'ent',
+ eli: 'e',
+ ousli: 'ous',
+ ization: 'ize',
+ ation: 'ate',
+ ator: 'ate',
+ alism: 'al',
+ iveness: 'ive',
+ fulness: 'ful',
+ ousness: 'ous',
+ aliti: 'al',
+ iviti: 'ive',
+ biliti: 'ble',
+ logi: 'log'
+ };
+
+ var step3list = {
+ icate: 'ic',
+ ative: '',
+ alize: 'al',
+ iciti: 'ic',
+ ical: 'ic',
+ ful: '',
+ ness: ''
+ };
+
+ var c = "[^aeiou]"; // consonant
+ var v = "[aeiouy]"; // vowel
+ var C = c + "[^aeiouy]*"; // consonant sequence
+ var V = v + "[aeiou]*"; // vowel sequence
+
+ var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
+ var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
+ var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
+ var s_v = "^(" + C + ")?" + v; // vowel in stem
+
+ this.stemWord = function (w) {
+ var stem;
+ var suffix;
+ var firstch;
+ var origword = w;
+
+ if (w.length < 3)
+ return w;
+
+ var re;
+ var re2;
+ var re3;
+ var re4;
+
+ firstch = w.substr(0,1);
+ if (firstch == "y")
+ w = firstch.toUpperCase() + w.substr(1);
+
+ // Step 1a
+ re = /^(.+?)(ss|i)es$/;
+ re2 = /^(.+?)([^s])s$/;
+
+ if (re.test(w))
+ w = w.replace(re,"$1$2");
+ else if (re2.test(w))
+ w = w.replace(re2,"$1$2");
+
+ // Step 1b
+ re = /^(.+?)eed$/;
+ re2 = /^(.+?)(ed|ing)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ re = new RegExp(mgr0);
+ if (re.test(fp[1])) {
+ re = /.$/;
+ w = w.replace(re,"");
+ }
+ }
+ else if (re2.test(w)) {
+ var fp = re2.exec(w);
+ stem = fp[1];
+ re2 = new RegExp(s_v);
+ if (re2.test(stem)) {
+ w = stem;
+ re2 = /(at|bl|iz)$/;
+ re3 = new RegExp("([^aeiouylsz])\\1$");
+ re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
+ if (re2.test(w))
+ w = w + "e";
+ else if (re3.test(w)) {
+ re = /.$/;
+ w = w.replace(re,"");
+ }
+ else if (re4.test(w))
+ w = w + "e";
+ }
+ }
+
+ // Step 1c
+ re = /^(.+?)y$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = new RegExp(s_v);
+ if (re.test(stem))
+ w = stem + "i";
+ }
+
+ // Step 2
+ re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ suffix = fp[2];
+ re = new RegExp(mgr0);
+ if (re.test(stem))
+ w = stem + step2list[suffix];
+ }
+
+ // Step 3
+ re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ suffix = fp[2];
+ re = new RegExp(mgr0);
+ if (re.test(stem))
+ w = stem + step3list[suffix];
+ }
+
+ // Step 4
+ re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
+ re2 = /^(.+?)(s|t)(ion)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = new RegExp(mgr1);
+ if (re.test(stem))
+ w = stem;
+ }
+ else if (re2.test(w)) {
+ var fp = re2.exec(w);
+ stem = fp[1] + fp[2];
+ re2 = new RegExp(mgr1);
+ if (re2.test(stem))
+ w = stem;
+ }
+
+ // Step 5
+ re = /^(.+?)e$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = new RegExp(mgr1);
+ re2 = new RegExp(meq1);
+ re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
+ if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
+ w = stem;
+ }
+ re = /ll$/;
+ re2 = new RegExp(mgr1);
+ if (re.test(w) && re2.test(w)) {
+ re = /.$/;
+ w = w.replace(re,"");
+ }
+
+ // and turn initial Y back to y
+ if (firstch == "y")
+ w = firstch.toLowerCase() + w.substr(1);
+ return w;
+ }
+}
+
diff --git a/branch/main/_static/minipres.js b/branch/main/_static/minipres.js
new file mode 100644
index 0000000..ad11c87
--- /dev/null
+++ b/branch/main/_static/minipres.js
@@ -0,0 +1,223 @@
+// Add goTo method to elements
+// http://stackoverflow.com/questions/4801655/how-to-go-to-a-specific-element-on-page
+(function($) {
+ $.fn.goTo = function() {
+ $('html, body').animate({
+ scrollTop: $(this).offset().top //+ 'px'
+ }, 'fast');
+ return this; // for chaining...
+ }
+})(jQuery);
+
+// NO good way to do this!. Copy a hack from here
+// https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
+// https://stackoverflow.com/a/2880929
+var urlParams;
+(window.onpopstate = function () {
+ var match,
+ pl = /\+/g, // Regex for replacing addition symbol with a space
+ search = /([^&=]+)=?([^&]*)/g,
+ decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
+ query = window.location.search.substring(1);
+ urlParams = {};
+ while (match = search.exec(query))
+ urlParams[decode(match[1])] = decode(match[2]);
+})();
+
+// Select heading levels
+var maxHeading = urlParams['h']
+if (maxHeading === undefined) maxHeading = 2
+var headingLevels = [];
+for (h=2 ; h
(sections.length-1) ) {
+ // if we would scroll past bottom, or above top, do nothing
+ return;
+ }
+
+ console.log('xxxxxx');
+ var targetSection = sections[targetPos];
+ console.log(targetSection, typeof(targetSection));
+
+ // Return targetSection top and height
+ var secProperties = section_top_and_height(targetSection);
+ var top = secProperties['top'];
+ var height = secProperties['height']
+ var win_height = window.innerHeight;
+ //console.info(top, height, win_height)
+
+ var scroll_to = 0;
+ if (height >= win_height || height == 0) {
+ scroll_to = top;
+ } else {
+ scroll_to = top - (win_height-height)/3.;
+ }
+ //console.info(top, height, win_height, scroll_to)
+
+ $('html, body').animate({
+ scrollTop: scroll_to //+ 'px'
+ }, 'fast');
+
+}
+
+
+function minipres() {
+ /* Enable the minipres mode:
+ - call the hide() function
+ - set up the scrolling listener
+ */
+ document.addEventListener('keydown', function (event) {
+ switch(event.which) {
+ case 37: // left
+ switch_slide(-1);
+ event.preventDefault();
+ return false;
+ break;
+ //case 38: // up
+ case 39: // right
+ switch_slide(+1);
+ event.preventDefault();
+ return false;
+ break;
+ //case 40: // down
+ default:
+ return; // exit this handler for other keys
+ }
+ }, true)
+
+ hide()
+
+ // Increase space between sections
+ //$("div .section").css('margin-bottom', '50%');
+ $(sectionSelector).css('margin-top', '50%');
+
+ // Reduce size/color of other sections
+ if (hiddenSectionSelector.length > 0) {
+ var hideNodes = $(hiddenSectionSelector);
+ console.log(typeof hideNodes, hideNodes);
+ for (node in hideNodes) {
+ console.log("a", typeof node, node);
+ node = hideNodes[node]; // what's right way to iterate values?
+ console.log("b", typeof node, node);
+ if (node.parentNode && node.parentNode.className == "section") {
+ node = node.parentNode;
+ console.log("c", typeof node, node);
+ //node.css['transform'] = 'scale(.5)';
+ //node.css['transform-origin'] = 'top center';
+ $(node).css('color', 'lightgrey');
+ //$(node).css('font-size', '20%');
+ //$(node).css('visibility', 'collapse');
+ //ntahousnatouhasno;
+ }
+ }
+ }
+}
+
+function hide() {
+ /* Hide all non-essential elements on the page
+ */
+
+ // This is for sphinx_rst_theme and readthedocs
+ $(".wy-nav-side").remove();
+ $(".wy-nav-content-wrap").css('margin-left', 0);
+ $('.rst-versions').remove(); // readthedocs version selector
+
+ // Add other formats here.
+}
+
+
+var slideshow = minipres;
+
+if (window.location.search.match(/[?&](minipres|slideshow|pres)([=&]|$)/) ) {
+ //minipres()
+ window.addEventListener("load", minipres);
+} else if (window.location.search.match(/[?&](plain)([=&]|$)/) ) {
+ window.addEventListener("load", hide);
+}
diff --git a/branch/main/_static/minus.png b/branch/main/_static/minus.png
new file mode 100644
index 0000000..d96755f
Binary files /dev/null and b/branch/main/_static/minus.png differ
diff --git a/branch/main/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css b/branch/main/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css
new file mode 100644
index 0000000..3356631
--- /dev/null
+++ b/branch/main/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css
@@ -0,0 +1,2342 @@
+/* Variables */
+:root {
+ --mystnb-source-bg-color: #f7f7f7;
+ --mystnb-stdout-bg-color: #fcfcfc;
+ --mystnb-stderr-bg-color: #fdd;
+ --mystnb-traceback-bg-color: #fcfcfc;
+ --mystnb-source-border-color: #ccc;
+ --mystnb-source-margin-color: green;
+ --mystnb-stdout-border-color: #f7f7f7;
+ --mystnb-stderr-border-color: #f7f7f7;
+ --mystnb-traceback-border-color: #ffd6d6;
+ --mystnb-hide-prompt-opacity: 70%;
+ --mystnb-source-border-radius: .4em;
+ --mystnb-source-border-width: 1px;
+}
+
+/* Whole cell */
+div.container.cell {
+ padding-left: 0;
+ margin-bottom: 1em;
+}
+
+/* Removing all background formatting so we can control at the div level */
+.cell_input div.highlight,
+.cell_output pre,
+.cell_input pre,
+.cell_output .output {
+ border: none;
+ box-shadow: none;
+}
+
+.cell_output .output pre,
+.cell_input pre {
+ margin: 0px;
+}
+
+/* Input cells */
+div.cell div.cell_input,
+div.cell details.above-input>summary {
+ padding-left: 0em;
+ padding-right: 0em;
+ border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid;
+ background-color: var(--mystnb-source-bg-color);
+ border-left-color: var(--mystnb-source-margin-color);
+ border-left-width: medium;
+ border-radius: var(--mystnb-source-border-radius);
+}
+
+div.cell_input>div,
+div.cell_output div.output>div.highlight {
+ margin: 0em !important;
+ border: none !important;
+}
+
+/* All cell outputs */
+.cell_output {
+ padding-left: 1em;
+ padding-right: 0em;
+ margin-top: 1em;
+}
+
+/* Text outputs from cells */
+.cell_output .output.text_plain,
+.cell_output .output.traceback,
+.cell_output .output.stream,
+.cell_output .output.stderr {
+ margin-top: 1em;
+ margin-bottom: 0em;
+ box-shadow: none;
+}
+
+.cell_output .output.text_plain,
+.cell_output .output.stream {
+ background: var(--mystnb-stdout-bg-color);
+ border: 1px solid var(--mystnb-stdout-border-color);
+}
+
+.cell_output .output.stderr {
+ background: var(--mystnb-stderr-bg-color);
+ border: 1px solid var(--mystnb-stderr-border-color);
+}
+
+.cell_output .output.traceback {
+ background: var(--mystnb-traceback-bg-color);
+ border: 1px solid var(--mystnb-traceback-border-color);
+}
+
+/* Collapsible cell content */
+div.cell details.above-input div.cell_input {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-top: var(--mystnb-source-border-width) var(--mystnb-source-border-color) dashed;
+}
+
+div.cell div.cell_input.above-output-prompt {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+div.cell details.above-input>summary {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ border-bottom: var(--mystnb-source-border-width) var(--mystnb-source-border-color) dashed;
+ padding-left: 1em;
+ margin-bottom: 0;
+}
+
+div.cell details.above-output>summary {
+ background-color: var(--mystnb-source-bg-color);
+ padding-left: 1em;
+ padding-right: 0em;
+ border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid;
+ border-radius: var(--mystnb-source-border-radius);
+ border-left-color: var(--mystnb-source-margin-color);
+ border-left-width: medium;
+}
+
+div.cell details.below-input>summary {
+ background-color: var(--mystnb-source-bg-color);
+ padding-left: 1em;
+ padding-right: 0em;
+ border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid;
+ border-top: none;
+ border-bottom-left-radius: var(--mystnb-source-border-radius);
+ border-bottom-right-radius: var(--mystnb-source-border-radius);
+ border-left-color: var(--mystnb-source-margin-color);
+ border-left-width: medium;
+}
+
+div.cell details.hide>summary>span {
+ opacity: var(--mystnb-hide-prompt-opacity);
+}
+
+div.cell details.hide[open]>summary>span.collapsed {
+ display: none;
+}
+
+div.cell details.hide:not([open])>summary>span.expanded {
+ display: none;
+}
+
+@keyframes collapsed-fade-in {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
+div.cell details.hide[open]>summary~* {
+ -moz-animation: collapsed-fade-in 0.3s ease-in-out;
+ -webkit-animation: collapsed-fade-in 0.3s ease-in-out;
+ animation: collapsed-fade-in 0.3s ease-in-out;
+}
+
+/* Math align to the left */
+.cell_output .MathJax_Display {
+ text-align: left !important;
+}
+
+/* Pandas tables. Pulled from the Jupyter / nbsphinx CSS */
+div.cell_output table {
+ border: none;
+ border-collapse: collapse;
+ border-spacing: 0;
+ color: black;
+ font-size: 1em;
+ table-layout: fixed;
+}
+
+div.cell_output thead {
+ border-bottom: 1px solid black;
+ vertical-align: bottom;
+}
+
+div.cell_output tr,
+div.cell_output th,
+div.cell_output td {
+ text-align: right;
+ vertical-align: middle;
+ padding: 0.5em 0.5em;
+ line-height: normal;
+ white-space: normal;
+ max-width: none;
+ border: none;
+}
+
+div.cell_output th {
+ font-weight: bold;
+}
+
+div.cell_output tbody tr:nth-child(odd) {
+ background: #f5f5f5;
+}
+
+div.cell_output tbody tr:hover {
+ background: rgba(66, 165, 245, 0.2);
+}
+
+/** source code line numbers **/
+span.linenos {
+ opacity: 0.5;
+}
+
+/* Inline text from `paste` operation */
+
+span.pasted-text {
+ font-weight: bold;
+}
+
+span.pasted-inline img {
+ max-height: 2em;
+}
+
+tbody span.pasted-inline img {
+ max-height: none;
+}
+
+/* Font colors for translated ANSI escape sequences
+Color values are copied from Jupyter Notebook
+https://github.com/jupyter/notebook/blob/52581f8eda9b319eb0390ac77fe5903c38f81e3e/notebook/static/notebook/less/ansicolors.less#L14-L21
+Background colors from
+https://nbsphinx.readthedocs.io/en/latest/code-cells.html#ANSI-Colors
+*/
+div.highlight .-Color-Bold {
+ font-weight: bold;
+}
+
+div.highlight .-Color[class*=-Black] {
+ color: #3E424D
+}
+
+div.highlight .-Color[class*=-Red] {
+ color: #E75C58
+}
+
+div.highlight .-Color[class*=-Green] {
+ color: #00A250
+}
+
+div.highlight .-Color[class*=-Yellow] {
+ color: #DDB62B
+}
+
+div.highlight .-Color[class*=-Blue] {
+ color: #208FFB
+}
+
+div.highlight .-Color[class*=-Magenta] {
+ color: #D160C4
+}
+
+div.highlight .-Color[class*=-Cyan] {
+ color: #60C6C8
+}
+
+div.highlight .-Color[class*=-White] {
+ color: #C5C1B4
+}
+
+div.highlight .-Color[class*=-BGBlack] {
+ background-color: #3E424D
+}
+
+div.highlight .-Color[class*=-BGRed] {
+ background-color: #E75C58
+}
+
+div.highlight .-Color[class*=-BGGreen] {
+ background-color: #00A250
+}
+
+div.highlight .-Color[class*=-BGYellow] {
+ background-color: #DDB62B
+}
+
+div.highlight .-Color[class*=-BGBlue] {
+ background-color: #208FFB
+}
+
+div.highlight .-Color[class*=-BGMagenta] {
+ background-color: #D160C4
+}
+
+div.highlight .-Color[class*=-BGCyan] {
+ background-color: #60C6C8
+}
+
+div.highlight .-Color[class*=-BGWhite] {
+ background-color: #C5C1B4
+}
+
+/* Font colors for 8-bit ANSI */
+
+div.highlight .-Color[class*=-C0] {
+ color: #000000
+}
+
+div.highlight .-Color[class*=-BGC0] {
+ background-color: #000000
+}
+
+div.highlight .-Color[class*=-C1] {
+ color: #800000
+}
+
+div.highlight .-Color[class*=-BGC1] {
+ background-color: #800000
+}
+
+div.highlight .-Color[class*=-C2] {
+ color: #008000
+}
+
+div.highlight .-Color[class*=-BGC2] {
+ background-color: #008000
+}
+
+div.highlight .-Color[class*=-C3] {
+ color: #808000
+}
+
+div.highlight .-Color[class*=-BGC3] {
+ background-color: #808000
+}
+
+div.highlight .-Color[class*=-C4] {
+ color: #000080
+}
+
+div.highlight .-Color[class*=-BGC4] {
+ background-color: #000080
+}
+
+div.highlight .-Color[class*=-C5] {
+ color: #800080
+}
+
+div.highlight .-Color[class*=-BGC5] {
+ background-color: #800080
+}
+
+div.highlight .-Color[class*=-C6] {
+ color: #008080
+}
+
+div.highlight .-Color[class*=-BGC6] {
+ background-color: #008080
+}
+
+div.highlight .-Color[class*=-C7] {
+ color: #C0C0C0
+}
+
+div.highlight .-Color[class*=-BGC7] {
+ background-color: #C0C0C0
+}
+
+div.highlight .-Color[class*=-C8] {
+ color: #808080
+}
+
+div.highlight .-Color[class*=-BGC8] {
+ background-color: #808080
+}
+
+div.highlight .-Color[class*=-C9] {
+ color: #FF0000
+}
+
+div.highlight .-Color[class*=-BGC9] {
+ background-color: #FF0000
+}
+
+div.highlight .-Color[class*=-C10] {
+ color: #00FF00
+}
+
+div.highlight .-Color[class*=-BGC10] {
+ background-color: #00FF00
+}
+
+div.highlight .-Color[class*=-C11] {
+ color: #FFFF00
+}
+
+div.highlight .-Color[class*=-BGC11] {
+ background-color: #FFFF00
+}
+
+div.highlight .-Color[class*=-C12] {
+ color: #0000FF
+}
+
+div.highlight .-Color[class*=-BGC12] {
+ background-color: #0000FF
+}
+
+div.highlight .-Color[class*=-C13] {
+ color: #FF00FF
+}
+
+div.highlight .-Color[class*=-BGC13] {
+ background-color: #FF00FF
+}
+
+div.highlight .-Color[class*=-C14] {
+ color: #00FFFF
+}
+
+div.highlight .-Color[class*=-BGC14] {
+ background-color: #00FFFF
+}
+
+div.highlight .-Color[class*=-C15] {
+ color: #FFFFFF
+}
+
+div.highlight .-Color[class*=-BGC15] {
+ background-color: #FFFFFF
+}
+
+div.highlight .-Color[class*=-C16] {
+ color: #000000
+}
+
+div.highlight .-Color[class*=-BGC16] {
+ background-color: #000000
+}
+
+div.highlight .-Color[class*=-C17] {
+ color: #00005F
+}
+
+div.highlight .-Color[class*=-BGC17] {
+ background-color: #00005F
+}
+
+div.highlight .-Color[class*=-C18] {
+ color: #000087
+}
+
+div.highlight .-Color[class*=-BGC18] {
+ background-color: #000087
+}
+
+div.highlight .-Color[class*=-C19] {
+ color: #0000AF
+}
+
+div.highlight .-Color[class*=-BGC19] {
+ background-color: #0000AF
+}
+
+div.highlight .-Color[class*=-C20] {
+ color: #0000D7
+}
+
+div.highlight .-Color[class*=-BGC20] {
+ background-color: #0000D7
+}
+
+div.highlight .-Color[class*=-C21] {
+ color: #0000FF
+}
+
+div.highlight .-Color[class*=-BGC21] {
+ background-color: #0000FF
+}
+
+div.highlight .-Color[class*=-C22] {
+ color: #005F00
+}
+
+div.highlight .-Color[class*=-BGC22] {
+ background-color: #005F00
+}
+
+div.highlight .-Color[class*=-C23] {
+ color: #005F5F
+}
+
+div.highlight .-Color[class*=-BGC23] {
+ background-color: #005F5F
+}
+
+div.highlight .-Color[class*=-C24] {
+ color: #005F87
+}
+
+div.highlight .-Color[class*=-BGC24] {
+ background-color: #005F87
+}
+
+div.highlight .-Color[class*=-C25] {
+ color: #005FAF
+}
+
+div.highlight .-Color[class*=-BGC25] {
+ background-color: #005FAF
+}
+
+div.highlight .-Color[class*=-C26] {
+ color: #005FD7
+}
+
+div.highlight .-Color[class*=-BGC26] {
+ background-color: #005FD7
+}
+
+div.highlight .-Color[class*=-C27] {
+ color: #005FFF
+}
+
+div.highlight .-Color[class*=-BGC27] {
+ background-color: #005FFF
+}
+
+div.highlight .-Color[class*=-C28] {
+ color: #008700
+}
+
+div.highlight .-Color[class*=-BGC28] {
+ background-color: #008700
+}
+
+div.highlight .-Color[class*=-C29] {
+ color: #00875F
+}
+
+div.highlight .-Color[class*=-BGC29] {
+ background-color: #00875F
+}
+
+div.highlight .-Color[class*=-C30] {
+ color: #008787
+}
+
+div.highlight .-Color[class*=-BGC30] {
+ background-color: #008787
+}
+
+div.highlight .-Color[class*=-C31] {
+ color: #0087AF
+}
+
+div.highlight .-Color[class*=-BGC31] {
+ background-color: #0087AF
+}
+
+div.highlight .-Color[class*=-C32] {
+ color: #0087D7
+}
+
+div.highlight .-Color[class*=-BGC32] {
+ background-color: #0087D7
+}
+
+div.highlight .-Color[class*=-C33] {
+ color: #0087FF
+}
+
+div.highlight .-Color[class*=-BGC33] {
+ background-color: #0087FF
+}
+
+div.highlight .-Color[class*=-C34] {
+ color: #00AF00
+}
+
+div.highlight .-Color[class*=-BGC34] {
+ background-color: #00AF00
+}
+
+div.highlight .-Color[class*=-C35] {
+ color: #00AF5F
+}
+
+div.highlight .-Color[class*=-BGC35] {
+ background-color: #00AF5F
+}
+
+div.highlight .-Color[class*=-C36] {
+ color: #00AF87
+}
+
+div.highlight .-Color[class*=-BGC36] {
+ background-color: #00AF87
+}
+
+div.highlight .-Color[class*=-C37] {
+ color: #00AFAF
+}
+
+div.highlight .-Color[class*=-BGC37] {
+ background-color: #00AFAF
+}
+
+div.highlight .-Color[class*=-C38] {
+ color: #00AFD7
+}
+
+div.highlight .-Color[class*=-BGC38] {
+ background-color: #00AFD7
+}
+
+div.highlight .-Color[class*=-C39] {
+ color: #00AFFF
+}
+
+div.highlight .-Color[class*=-BGC39] {
+ background-color: #00AFFF
+}
+
+div.highlight .-Color[class*=-C40] {
+ color: #00D700
+}
+
+div.highlight .-Color[class*=-BGC40] {
+ background-color: #00D700
+}
+
+div.highlight .-Color[class*=-C41] {
+ color: #00D75F
+}
+
+div.highlight .-Color[class*=-BGC41] {
+ background-color: #00D75F
+}
+
+div.highlight .-Color[class*=-C42] {
+ color: #00D787
+}
+
+div.highlight .-Color[class*=-BGC42] {
+ background-color: #00D787
+}
+
+div.highlight .-Color[class*=-C43] {
+ color: #00D7AF
+}
+
+div.highlight .-Color[class*=-BGC43] {
+ background-color: #00D7AF
+}
+
+div.highlight .-Color[class*=-C44] {
+ color: #00D7D7
+}
+
+div.highlight .-Color[class*=-BGC44] {
+ background-color: #00D7D7
+}
+
+div.highlight .-Color[class*=-C45] {
+ color: #00D7FF
+}
+
+div.highlight .-Color[class*=-BGC45] {
+ background-color: #00D7FF
+}
+
+div.highlight .-Color[class*=-C46] {
+ color: #00FF00
+}
+
+div.highlight .-Color[class*=-BGC46] {
+ background-color: #00FF00
+}
+
+div.highlight .-Color[class*=-C47] {
+ color: #00FF5F
+}
+
+div.highlight .-Color[class*=-BGC47] {
+ background-color: #00FF5F
+}
+
+div.highlight .-Color[class*=-C48] {
+ color: #00FF87
+}
+
+div.highlight .-Color[class*=-BGC48] {
+ background-color: #00FF87
+}
+
+div.highlight .-Color[class*=-C49] {
+ color: #00FFAF
+}
+
+div.highlight .-Color[class*=-BGC49] {
+ background-color: #00FFAF
+}
+
+div.highlight .-Color[class*=-C50] {
+ color: #00FFD7
+}
+
+div.highlight .-Color[class*=-BGC50] {
+ background-color: #00FFD7
+}
+
+div.highlight .-Color[class*=-C51] {
+ color: #00FFFF
+}
+
+div.highlight .-Color[class*=-BGC51] {
+ background-color: #00FFFF
+}
+
+div.highlight .-Color[class*=-C52] {
+ color: #5F0000
+}
+
+div.highlight .-Color[class*=-BGC52] {
+ background-color: #5F0000
+}
+
+div.highlight .-Color[class*=-C53] {
+ color: #5F005F
+}
+
+div.highlight .-Color[class*=-BGC53] {
+ background-color: #5F005F
+}
+
+div.highlight .-Color[class*=-C54] {
+ color: #5F0087
+}
+
+div.highlight .-Color[class*=-BGC54] {
+ background-color: #5F0087
+}
+
+div.highlight .-Color[class*=-C55] {
+ color: #5F00AF
+}
+
+div.highlight .-Color[class*=-BGC55] {
+ background-color: #5F00AF
+}
+
+div.highlight .-Color[class*=-C56] {
+ color: #5F00D7
+}
+
+div.highlight .-Color[class*=-BGC56] {
+ background-color: #5F00D7
+}
+
+div.highlight .-Color[class*=-C57] {
+ color: #5F00FF
+}
+
+div.highlight .-Color[class*=-BGC57] {
+ background-color: #5F00FF
+}
+
+div.highlight .-Color[class*=-C58] {
+ color: #5F5F00
+}
+
+div.highlight .-Color[class*=-BGC58] {
+ background-color: #5F5F00
+}
+
+div.highlight .-Color[class*=-C59] {
+ color: #5F5F5F
+}
+
+div.highlight .-Color[class*=-BGC59] {
+ background-color: #5F5F5F
+}
+
+div.highlight .-Color[class*=-C60] {
+ color: #5F5F87
+}
+
+div.highlight .-Color[class*=-BGC60] {
+ background-color: #5F5F87
+}
+
+div.highlight .-Color[class*=-C61] {
+ color: #5F5FAF
+}
+
+div.highlight .-Color[class*=-BGC61] {
+ background-color: #5F5FAF
+}
+
+div.highlight .-Color[class*=-C62] {
+ color: #5F5FD7
+}
+
+div.highlight .-Color[class*=-BGC62] {
+ background-color: #5F5FD7
+}
+
+div.highlight .-Color[class*=-C63] {
+ color: #5F5FFF
+}
+
+div.highlight .-Color[class*=-BGC63] {
+ background-color: #5F5FFF
+}
+
+div.highlight .-Color[class*=-C64] {
+ color: #5F8700
+}
+
+div.highlight .-Color[class*=-BGC64] {
+ background-color: #5F8700
+}
+
+div.highlight .-Color[class*=-C65] {
+ color: #5F875F
+}
+
+div.highlight .-Color[class*=-BGC65] {
+ background-color: #5F875F
+}
+
+div.highlight .-Color[class*=-C66] {
+ color: #5F8787
+}
+
+div.highlight .-Color[class*=-BGC66] {
+ background-color: #5F8787
+}
+
+div.highlight .-Color[class*=-C67] {
+ color: #5F87AF
+}
+
+div.highlight .-Color[class*=-BGC67] {
+ background-color: #5F87AF
+}
+
+div.highlight .-Color[class*=-C68] {
+ color: #5F87D7
+}
+
+div.highlight .-Color[class*=-BGC68] {
+ background-color: #5F87D7
+}
+
+div.highlight .-Color[class*=-C69] {
+ color: #5F87FF
+}
+
+div.highlight .-Color[class*=-BGC69] {
+ background-color: #5F87FF
+}
+
+div.highlight .-Color[class*=-C70] {
+ color: #5FAF00
+}
+
+div.highlight .-Color[class*=-BGC70] {
+ background-color: #5FAF00
+}
+
+div.highlight .-Color[class*=-C71] {
+ color: #5FAF5F
+}
+
+div.highlight .-Color[class*=-BGC71] {
+ background-color: #5FAF5F
+}
+
+div.highlight .-Color[class*=-C72] {
+ color: #5FAF87
+}
+
+div.highlight .-Color[class*=-BGC72] {
+ background-color: #5FAF87
+}
+
+div.highlight .-Color[class*=-C73] {
+ color: #5FAFAF
+}
+
+div.highlight .-Color[class*=-BGC73] {
+ background-color: #5FAFAF
+}
+
+div.highlight .-Color[class*=-C74] {
+ color: #5FAFD7
+}
+
+div.highlight .-Color[class*=-BGC74] {
+ background-color: #5FAFD7
+}
+
+div.highlight .-Color[class*=-C75] {
+ color: #5FAFFF
+}
+
+div.highlight .-Color[class*=-BGC75] {
+ background-color: #5FAFFF
+}
+
+div.highlight .-Color[class*=-C76] {
+ color: #5FD700
+}
+
+div.highlight .-Color[class*=-BGC76] {
+ background-color: #5FD700
+}
+
+div.highlight .-Color[class*=-C77] {
+ color: #5FD75F
+}
+
+div.highlight .-Color[class*=-BGC77] {
+ background-color: #5FD75F
+}
+
+div.highlight .-Color[class*=-C78] {
+ color: #5FD787
+}
+
+div.highlight .-Color[class*=-BGC78] {
+ background-color: #5FD787
+}
+
+div.highlight .-Color[class*=-C79] {
+ color: #5FD7AF
+}
+
+div.highlight .-Color[class*=-BGC79] {
+ background-color: #5FD7AF
+}
+
+div.highlight .-Color[class*=-C80] {
+ color: #5FD7D7
+}
+
+div.highlight .-Color[class*=-BGC80] {
+ background-color: #5FD7D7
+}
+
+div.highlight .-Color[class*=-C81] {
+ color: #5FD7FF
+}
+
+div.highlight .-Color[class*=-BGC81] {
+ background-color: #5FD7FF
+}
+
+div.highlight .-Color[class*=-C82] {
+ color: #5FFF00
+}
+
+div.highlight .-Color[class*=-BGC82] {
+ background-color: #5FFF00
+}
+
+div.highlight .-Color[class*=-C83] {
+ color: #5FFF5F
+}
+
+div.highlight .-Color[class*=-BGC83] {
+ background-color: #5FFF5F
+}
+
+div.highlight .-Color[class*=-C84] {
+ color: #5FFF87
+}
+
+div.highlight .-Color[class*=-BGC84] {
+ background-color: #5FFF87
+}
+
+div.highlight .-Color[class*=-C85] {
+ color: #5FFFAF
+}
+
+div.highlight .-Color[class*=-BGC85] {
+ background-color: #5FFFAF
+}
+
+div.highlight .-Color[class*=-C86] {
+ color: #5FFFD7
+}
+
+div.highlight .-Color[class*=-BGC86] {
+ background-color: #5FFFD7
+}
+
+div.highlight .-Color[class*=-C87] {
+ color: #5FFFFF
+}
+
+div.highlight .-Color[class*=-BGC87] {
+ background-color: #5FFFFF
+}
+
+div.highlight .-Color[class*=-C88] {
+ color: #870000
+}
+
+div.highlight .-Color[class*=-BGC88] {
+ background-color: #870000
+}
+
+div.highlight .-Color[class*=-C89] {
+ color: #87005F
+}
+
+div.highlight .-Color[class*=-BGC89] {
+ background-color: #87005F
+}
+
+div.highlight .-Color[class*=-C90] {
+ color: #870087
+}
+
+div.highlight .-Color[class*=-BGC90] {
+ background-color: #870087
+}
+
+div.highlight .-Color[class*=-C91] {
+ color: #8700AF
+}
+
+div.highlight .-Color[class*=-BGC91] {
+ background-color: #8700AF
+}
+
+div.highlight .-Color[class*=-C92] {
+ color: #8700D7
+}
+
+div.highlight .-Color[class*=-BGC92] {
+ background-color: #8700D7
+}
+
+div.highlight .-Color[class*=-C93] {
+ color: #8700FF
+}
+
+div.highlight .-Color[class*=-BGC93] {
+ background-color: #8700FF
+}
+
+div.highlight .-Color[class*=-C94] {
+ color: #875F00
+}
+
+div.highlight .-Color[class*=-BGC94] {
+ background-color: #875F00
+}
+
+div.highlight .-Color[class*=-C95] {
+ color: #875F5F
+}
+
+div.highlight .-Color[class*=-BGC95] {
+ background-color: #875F5F
+}
+
+div.highlight .-Color[class*=-C96] {
+ color: #875F87
+}
+
+div.highlight .-Color[class*=-BGC96] {
+ background-color: #875F87
+}
+
+div.highlight .-Color[class*=-C97] {
+ color: #875FAF
+}
+
+div.highlight .-Color[class*=-BGC97] {
+ background-color: #875FAF
+}
+
+div.highlight .-Color[class*=-C98] {
+ color: #875FD7
+}
+
+div.highlight .-Color[class*=-BGC98] {
+ background-color: #875FD7
+}
+
+div.highlight .-Color[class*=-C99] {
+ color: #875FFF
+}
+
+div.highlight .-Color[class*=-BGC99] {
+ background-color: #875FFF
+}
+
+div.highlight .-Color[class*=-C100] {
+ color: #878700
+}
+
+div.highlight .-Color[class*=-BGC100] {
+ background-color: #878700
+}
+
+div.highlight .-Color[class*=-C101] {
+ color: #87875F
+}
+
+div.highlight .-Color[class*=-BGC101] {
+ background-color: #87875F
+}
+
+div.highlight .-Color[class*=-C102] {
+ color: #878787
+}
+
+div.highlight .-Color[class*=-BGC102] {
+ background-color: #878787
+}
+
+div.highlight .-Color[class*=-C103] {
+ color: #8787AF
+}
+
+div.highlight .-Color[class*=-BGC103] {
+ background-color: #8787AF
+}
+
+div.highlight .-Color[class*=-C104] {
+ color: #8787D7
+}
+
+div.highlight .-Color[class*=-BGC104] {
+ background-color: #8787D7
+}
+
+div.highlight .-Color[class*=-C105] {
+ color: #8787FF
+}
+
+div.highlight .-Color[class*=-BGC105] {
+ background-color: #8787FF
+}
+
+div.highlight .-Color[class*=-C106] {
+ color: #87AF00
+}
+
+div.highlight .-Color[class*=-BGC106] {
+ background-color: #87AF00
+}
+
+div.highlight .-Color[class*=-C107] {
+ color: #87AF5F
+}
+
+div.highlight .-Color[class*=-BGC107] {
+ background-color: #87AF5F
+}
+
+div.highlight .-Color[class*=-C108] {
+ color: #87AF87
+}
+
+div.highlight .-Color[class*=-BGC108] {
+ background-color: #87AF87
+}
+
+div.highlight .-Color[class*=-C109] {
+ color: #87AFAF
+}
+
+div.highlight .-Color[class*=-BGC109] {
+ background-color: #87AFAF
+}
+
+div.highlight .-Color[class*=-C110] {
+ color: #87AFD7
+}
+
+div.highlight .-Color[class*=-BGC110] {
+ background-color: #87AFD7
+}
+
+div.highlight .-Color[class*=-C111] {
+ color: #87AFFF
+}
+
+div.highlight .-Color[class*=-BGC111] {
+ background-color: #87AFFF
+}
+
+div.highlight .-Color[class*=-C112] {
+ color: #87D700
+}
+
+div.highlight .-Color[class*=-BGC112] {
+ background-color: #87D700
+}
+
+div.highlight .-Color[class*=-C113] {
+ color: #87D75F
+}
+
+div.highlight .-Color[class*=-BGC113] {
+ background-color: #87D75F
+}
+
+div.highlight .-Color[class*=-C114] {
+ color: #87D787
+}
+
+div.highlight .-Color[class*=-BGC114] {
+ background-color: #87D787
+}
+
+div.highlight .-Color[class*=-C115] {
+ color: #87D7AF
+}
+
+div.highlight .-Color[class*=-BGC115] {
+ background-color: #87D7AF
+}
+
+div.highlight .-Color[class*=-C116] {
+ color: #87D7D7
+}
+
+div.highlight .-Color[class*=-BGC116] {
+ background-color: #87D7D7
+}
+
+div.highlight .-Color[class*=-C117] {
+ color: #87D7FF
+}
+
+div.highlight .-Color[class*=-BGC117] {
+ background-color: #87D7FF
+}
+
+div.highlight .-Color[class*=-C118] {
+ color: #87FF00
+}
+
+div.highlight .-Color[class*=-BGC118] {
+ background-color: #87FF00
+}
+
+div.highlight .-Color[class*=-C119] {
+ color: #87FF5F
+}
+
+div.highlight .-Color[class*=-BGC119] {
+ background-color: #87FF5F
+}
+
+div.highlight .-Color[class*=-C120] {
+ color: #87FF87
+}
+
+div.highlight .-Color[class*=-BGC120] {
+ background-color: #87FF87
+}
+
+div.highlight .-Color[class*=-C121] {
+ color: #87FFAF
+}
+
+div.highlight .-Color[class*=-BGC121] {
+ background-color: #87FFAF
+}
+
+div.highlight .-Color[class*=-C122] {
+ color: #87FFD7
+}
+
+div.highlight .-Color[class*=-BGC122] {
+ background-color: #87FFD7
+}
+
+div.highlight .-Color[class*=-C123] {
+ color: #87FFFF
+}
+
+div.highlight .-Color[class*=-BGC123] {
+ background-color: #87FFFF
+}
+
+div.highlight .-Color[class*=-C124] {
+ color: #AF0000
+}
+
+div.highlight .-Color[class*=-BGC124] {
+ background-color: #AF0000
+}
+
+div.highlight .-Color[class*=-C125] {
+ color: #AF005F
+}
+
+div.highlight .-Color[class*=-BGC125] {
+ background-color: #AF005F
+}
+
+div.highlight .-Color[class*=-C126] {
+ color: #AF0087
+}
+
+div.highlight .-Color[class*=-BGC126] {
+ background-color: #AF0087
+}
+
+div.highlight .-Color[class*=-C127] {
+ color: #AF00AF
+}
+
+div.highlight .-Color[class*=-BGC127] {
+ background-color: #AF00AF
+}
+
+div.highlight .-Color[class*=-C128] {
+ color: #AF00D7
+}
+
+div.highlight .-Color[class*=-BGC128] {
+ background-color: #AF00D7
+}
+
+div.highlight .-Color[class*=-C129] {
+ color: #AF00FF
+}
+
+div.highlight .-Color[class*=-BGC129] {
+ background-color: #AF00FF
+}
+
+div.highlight .-Color[class*=-C130] {
+ color: #AF5F00
+}
+
+div.highlight .-Color[class*=-BGC130] {
+ background-color: #AF5F00
+}
+
+div.highlight .-Color[class*=-C131] {
+ color: #AF5F5F
+}
+
+div.highlight .-Color[class*=-BGC131] {
+ background-color: #AF5F5F
+}
+
+div.highlight .-Color[class*=-C132] {
+ color: #AF5F87
+}
+
+div.highlight .-Color[class*=-BGC132] {
+ background-color: #AF5F87
+}
+
+div.highlight .-Color[class*=-C133] {
+ color: #AF5FAF
+}
+
+div.highlight .-Color[class*=-BGC133] {
+ background-color: #AF5FAF
+}
+
+div.highlight .-Color[class*=-C134] {
+ color: #AF5FD7
+}
+
+div.highlight .-Color[class*=-BGC134] {
+ background-color: #AF5FD7
+}
+
+div.highlight .-Color[class*=-C135] {
+ color: #AF5FFF
+}
+
+div.highlight .-Color[class*=-BGC135] {
+ background-color: #AF5FFF
+}
+
+div.highlight .-Color[class*=-C136] {
+ color: #AF8700
+}
+
+div.highlight .-Color[class*=-BGC136] {
+ background-color: #AF8700
+}
+
+div.highlight .-Color[class*=-C137] {
+ color: #AF875F
+}
+
+div.highlight .-Color[class*=-BGC137] {
+ background-color: #AF875F
+}
+
+div.highlight .-Color[class*=-C138] {
+ color: #AF8787
+}
+
+div.highlight .-Color[class*=-BGC138] {
+ background-color: #AF8787
+}
+
+div.highlight .-Color[class*=-C139] {
+ color: #AF87AF
+}
+
+div.highlight .-Color[class*=-BGC139] {
+ background-color: #AF87AF
+}
+
+div.highlight .-Color[class*=-C140] {
+ color: #AF87D7
+}
+
+div.highlight .-Color[class*=-BGC140] {
+ background-color: #AF87D7
+}
+
+div.highlight .-Color[class*=-C141] {
+ color: #AF87FF
+}
+
+div.highlight .-Color[class*=-BGC141] {
+ background-color: #AF87FF
+}
+
+div.highlight .-Color[class*=-C142] {
+ color: #AFAF00
+}
+
+div.highlight .-Color[class*=-BGC142] {
+ background-color: #AFAF00
+}
+
+div.highlight .-Color[class*=-C143] {
+ color: #AFAF5F
+}
+
+div.highlight .-Color[class*=-BGC143] {
+ background-color: #AFAF5F
+}
+
+div.highlight .-Color[class*=-C144] {
+ color: #AFAF87
+}
+
+div.highlight .-Color[class*=-BGC144] {
+ background-color: #AFAF87
+}
+
+div.highlight .-Color[class*=-C145] {
+ color: #AFAFAF
+}
+
+div.highlight .-Color[class*=-BGC145] {
+ background-color: #AFAFAF
+}
+
+div.highlight .-Color[class*=-C146] {
+ color: #AFAFD7
+}
+
+div.highlight .-Color[class*=-BGC146] {
+ background-color: #AFAFD7
+}
+
+div.highlight .-Color[class*=-C147] {
+ color: #AFAFFF
+}
+
+div.highlight .-Color[class*=-BGC147] {
+ background-color: #AFAFFF
+}
+
+div.highlight .-Color[class*=-C148] {
+ color: #AFD700
+}
+
+div.highlight .-Color[class*=-BGC148] {
+ background-color: #AFD700
+}
+
+div.highlight .-Color[class*=-C149] {
+ color: #AFD75F
+}
+
+div.highlight .-Color[class*=-BGC149] {
+ background-color: #AFD75F
+}
+
+div.highlight .-Color[class*=-C150] {
+ color: #AFD787
+}
+
+div.highlight .-Color[class*=-BGC150] {
+ background-color: #AFD787
+}
+
+div.highlight .-Color[class*=-C151] {
+ color: #AFD7AF
+}
+
+div.highlight .-Color[class*=-BGC151] {
+ background-color: #AFD7AF
+}
+
+div.highlight .-Color[class*=-C152] {
+ color: #AFD7D7
+}
+
+div.highlight .-Color[class*=-BGC152] {
+ background-color: #AFD7D7
+}
+
+div.highlight .-Color[class*=-C153] {
+ color: #AFD7FF
+}
+
+div.highlight .-Color[class*=-BGC153] {
+ background-color: #AFD7FF
+}
+
+div.highlight .-Color[class*=-C154] {
+ color: #AFFF00
+}
+
+div.highlight .-Color[class*=-BGC154] {
+ background-color: #AFFF00
+}
+
+div.highlight .-Color[class*=-C155] {
+ color: #AFFF5F
+}
+
+div.highlight .-Color[class*=-BGC155] {
+ background-color: #AFFF5F
+}
+
+div.highlight .-Color[class*=-C156] {
+ color: #AFFF87
+}
+
+div.highlight .-Color[class*=-BGC156] {
+ background-color: #AFFF87
+}
+
+div.highlight .-Color[class*=-C157] {
+ color: #AFFFAF
+}
+
+div.highlight .-Color[class*=-BGC157] {
+ background-color: #AFFFAF
+}
+
+div.highlight .-Color[class*=-C158] {
+ color: #AFFFD7
+}
+
+div.highlight .-Color[class*=-BGC158] {
+ background-color: #AFFFD7
+}
+
+div.highlight .-Color[class*=-C159] {
+ color: #AFFFFF
+}
+
+div.highlight .-Color[class*=-BGC159] {
+ background-color: #AFFFFF
+}
+
+div.highlight .-Color[class*=-C160] {
+ color: #D70000
+}
+
+div.highlight .-Color[class*=-BGC160] {
+ background-color: #D70000
+}
+
+div.highlight .-Color[class*=-C161] {
+ color: #D7005F
+}
+
+div.highlight .-Color[class*=-BGC161] {
+ background-color: #D7005F
+}
+
+div.highlight .-Color[class*=-C162] {
+ color: #D70087
+}
+
+div.highlight .-Color[class*=-BGC162] {
+ background-color: #D70087
+}
+
+div.highlight .-Color[class*=-C163] {
+ color: #D700AF
+}
+
+div.highlight .-Color[class*=-BGC163] {
+ background-color: #D700AF
+}
+
+div.highlight .-Color[class*=-C164] {
+ color: #D700D7
+}
+
+div.highlight .-Color[class*=-BGC164] {
+ background-color: #D700D7
+}
+
+div.highlight .-Color[class*=-C165] {
+ color: #D700FF
+}
+
+div.highlight .-Color[class*=-BGC165] {
+ background-color: #D700FF
+}
+
+div.highlight .-Color[class*=-C166] {
+ color: #D75F00
+}
+
+div.highlight .-Color[class*=-BGC166] {
+ background-color: #D75F00
+}
+
+div.highlight .-Color[class*=-C167] {
+ color: #D75F5F
+}
+
+div.highlight .-Color[class*=-BGC167] {
+ background-color: #D75F5F
+}
+
+div.highlight .-Color[class*=-C168] {
+ color: #D75F87
+}
+
+div.highlight .-Color[class*=-BGC168] {
+ background-color: #D75F87
+}
+
+div.highlight .-Color[class*=-C169] {
+ color: #D75FAF
+}
+
+div.highlight .-Color[class*=-BGC169] {
+ background-color: #D75FAF
+}
+
+div.highlight .-Color[class*=-C170] {
+ color: #D75FD7
+}
+
+div.highlight .-Color[class*=-BGC170] {
+ background-color: #D75FD7
+}
+
+div.highlight .-Color[class*=-C171] {
+ color: #D75FFF
+}
+
+div.highlight .-Color[class*=-BGC171] {
+ background-color: #D75FFF
+}
+
+div.highlight .-Color[class*=-C172] {
+ color: #D78700
+}
+
+div.highlight .-Color[class*=-BGC172] {
+ background-color: #D78700
+}
+
+div.highlight .-Color[class*=-C173] {
+ color: #D7875F
+}
+
+div.highlight .-Color[class*=-BGC173] {
+ background-color: #D7875F
+}
+
+div.highlight .-Color[class*=-C174] {
+ color: #D78787
+}
+
+div.highlight .-Color[class*=-BGC174] {
+ background-color: #D78787
+}
+
+div.highlight .-Color[class*=-C175] {
+ color: #D787AF
+}
+
+div.highlight .-Color[class*=-BGC175] {
+ background-color: #D787AF
+}
+
+div.highlight .-Color[class*=-C176] {
+ color: #D787D7
+}
+
+div.highlight .-Color[class*=-BGC176] {
+ background-color: #D787D7
+}
+
+div.highlight .-Color[class*=-C177] {
+ color: #D787FF
+}
+
+div.highlight .-Color[class*=-BGC177] {
+ background-color: #D787FF
+}
+
+div.highlight .-Color[class*=-C178] {
+ color: #D7AF00
+}
+
+div.highlight .-Color[class*=-BGC178] {
+ background-color: #D7AF00
+}
+
+div.highlight .-Color[class*=-C179] {
+ color: #D7AF5F
+}
+
+div.highlight .-Color[class*=-BGC179] {
+ background-color: #D7AF5F
+}
+
+div.highlight .-Color[class*=-C180] {
+ color: #D7AF87
+}
+
+div.highlight .-Color[class*=-BGC180] {
+ background-color: #D7AF87
+}
+
+div.highlight .-Color[class*=-C181] {
+ color: #D7AFAF
+}
+
+div.highlight .-Color[class*=-BGC181] {
+ background-color: #D7AFAF
+}
+
+div.highlight .-Color[class*=-C182] {
+ color: #D7AFD7
+}
+
+div.highlight .-Color[class*=-BGC182] {
+ background-color: #D7AFD7
+}
+
+div.highlight .-Color[class*=-C183] {
+ color: #D7AFFF
+}
+
+div.highlight .-Color[class*=-BGC183] {
+ background-color: #D7AFFF
+}
+
+div.highlight .-Color[class*=-C184] {
+ color: #D7D700
+}
+
+div.highlight .-Color[class*=-BGC184] {
+ background-color: #D7D700
+}
+
+div.highlight .-Color[class*=-C185] {
+ color: #D7D75F
+}
+
+div.highlight .-Color[class*=-BGC185] {
+ background-color: #D7D75F
+}
+
+div.highlight .-Color[class*=-C186] {
+ color: #D7D787
+}
+
+div.highlight .-Color[class*=-BGC186] {
+ background-color: #D7D787
+}
+
+div.highlight .-Color[class*=-C187] {
+ color: #D7D7AF
+}
+
+div.highlight .-Color[class*=-BGC187] {
+ background-color: #D7D7AF
+}
+
+div.highlight .-Color[class*=-C188] {
+ color: #D7D7D7
+}
+
+div.highlight .-Color[class*=-BGC188] {
+ background-color: #D7D7D7
+}
+
+div.highlight .-Color[class*=-C189] {
+ color: #D7D7FF
+}
+
+div.highlight .-Color[class*=-BGC189] {
+ background-color: #D7D7FF
+}
+
+div.highlight .-Color[class*=-C190] {
+ color: #D7FF00
+}
+
+div.highlight .-Color[class*=-BGC190] {
+ background-color: #D7FF00
+}
+
+div.highlight .-Color[class*=-C191] {
+ color: #D7FF5F
+}
+
+div.highlight .-Color[class*=-BGC191] {
+ background-color: #D7FF5F
+}
+
+div.highlight .-Color[class*=-C192] {
+ color: #D7FF87
+}
+
+div.highlight .-Color[class*=-BGC192] {
+ background-color: #D7FF87
+}
+
+div.highlight .-Color[class*=-C193] {
+ color: #D7FFAF
+}
+
+div.highlight .-Color[class*=-BGC193] {
+ background-color: #D7FFAF
+}
+
+div.highlight .-Color[class*=-C194] {
+ color: #D7FFD7
+}
+
+div.highlight .-Color[class*=-BGC194] {
+ background-color: #D7FFD7
+}
+
+div.highlight .-Color[class*=-C195] {
+ color: #D7FFFF
+}
+
+div.highlight .-Color[class*=-BGC195] {
+ background-color: #D7FFFF
+}
+
+div.highlight .-Color[class*=-C196] {
+ color: #FF0000
+}
+
+div.highlight .-Color[class*=-BGC196] {
+ background-color: #FF0000
+}
+
+div.highlight .-Color[class*=-C197] {
+ color: #FF005F
+}
+
+div.highlight .-Color[class*=-BGC197] {
+ background-color: #FF005F
+}
+
+div.highlight .-Color[class*=-C198] {
+ color: #FF0087
+}
+
+div.highlight .-Color[class*=-BGC198] {
+ background-color: #FF0087
+}
+
+div.highlight .-Color[class*=-C199] {
+ color: #FF00AF
+}
+
+div.highlight .-Color[class*=-BGC199] {
+ background-color: #FF00AF
+}
+
+div.highlight .-Color[class*=-C200] {
+ color: #FF00D7
+}
+
+div.highlight .-Color[class*=-BGC200] {
+ background-color: #FF00D7
+}
+
+div.highlight .-Color[class*=-C201] {
+ color: #FF00FF
+}
+
+div.highlight .-Color[class*=-BGC201] {
+ background-color: #FF00FF
+}
+
+div.highlight .-Color[class*=-C202] {
+ color: #FF5F00
+}
+
+div.highlight .-Color[class*=-BGC202] {
+ background-color: #FF5F00
+}
+
+div.highlight .-Color[class*=-C203] {
+ color: #FF5F5F
+}
+
+div.highlight .-Color[class*=-BGC203] {
+ background-color: #FF5F5F
+}
+
+div.highlight .-Color[class*=-C204] {
+ color: #FF5F87
+}
+
+div.highlight .-Color[class*=-BGC204] {
+ background-color: #FF5F87
+}
+
+div.highlight .-Color[class*=-C205] {
+ color: #FF5FAF
+}
+
+div.highlight .-Color[class*=-BGC205] {
+ background-color: #FF5FAF
+}
+
+div.highlight .-Color[class*=-C206] {
+ color: #FF5FD7
+}
+
+div.highlight .-Color[class*=-BGC206] {
+ background-color: #FF5FD7
+}
+
+div.highlight .-Color[class*=-C207] {
+ color: #FF5FFF
+}
+
+div.highlight .-Color[class*=-BGC207] {
+ background-color: #FF5FFF
+}
+
+div.highlight .-Color[class*=-C208] {
+ color: #FF8700
+}
+
+div.highlight .-Color[class*=-BGC208] {
+ background-color: #FF8700
+}
+
+div.highlight .-Color[class*=-C209] {
+ color: #FF875F
+}
+
+div.highlight .-Color[class*=-BGC209] {
+ background-color: #FF875F
+}
+
+div.highlight .-Color[class*=-C210] {
+ color: #FF8787
+}
+
+div.highlight .-Color[class*=-BGC210] {
+ background-color: #FF8787
+}
+
+div.highlight .-Color[class*=-C211] {
+ color: #FF87AF
+}
+
+div.highlight .-Color[class*=-BGC211] {
+ background-color: #FF87AF
+}
+
+div.highlight .-Color[class*=-C212] {
+ color: #FF87D7
+}
+
+div.highlight .-Color[class*=-BGC212] {
+ background-color: #FF87D7
+}
+
+div.highlight .-Color[class*=-C213] {
+ color: #FF87FF
+}
+
+div.highlight .-Color[class*=-BGC213] {
+ background-color: #FF87FF
+}
+
+div.highlight .-Color[class*=-C214] {
+ color: #FFAF00
+}
+
+div.highlight .-Color[class*=-BGC214] {
+ background-color: #FFAF00
+}
+
+div.highlight .-Color[class*=-C215] {
+ color: #FFAF5F
+}
+
+div.highlight .-Color[class*=-BGC215] {
+ background-color: #FFAF5F
+}
+
+div.highlight .-Color[class*=-C216] {
+ color: #FFAF87
+}
+
+div.highlight .-Color[class*=-BGC216] {
+ background-color: #FFAF87
+}
+
+div.highlight .-Color[class*=-C217] {
+ color: #FFAFAF
+}
+
+div.highlight .-Color[class*=-BGC217] {
+ background-color: #FFAFAF
+}
+
+div.highlight .-Color[class*=-C218] {
+ color: #FFAFD7
+}
+
+div.highlight .-Color[class*=-BGC218] {
+ background-color: #FFAFD7
+}
+
+div.highlight .-Color[class*=-C219] {
+ color: #FFAFFF
+}
+
+div.highlight .-Color[class*=-BGC219] {
+ background-color: #FFAFFF
+}
+
+div.highlight .-Color[class*=-C220] {
+ color: #FFD700
+}
+
+div.highlight .-Color[class*=-BGC220] {
+ background-color: #FFD700
+}
+
+div.highlight .-Color[class*=-C221] {
+ color: #FFD75F
+}
+
+div.highlight .-Color[class*=-BGC221] {
+ background-color: #FFD75F
+}
+
+div.highlight .-Color[class*=-C222] {
+ color: #FFD787
+}
+
+div.highlight .-Color[class*=-BGC222] {
+ background-color: #FFD787
+}
+
+div.highlight .-Color[class*=-C223] {
+ color: #FFD7AF
+}
+
+div.highlight .-Color[class*=-BGC223] {
+ background-color: #FFD7AF
+}
+
+div.highlight .-Color[class*=-C224] {
+ color: #FFD7D7
+}
+
+div.highlight .-Color[class*=-BGC224] {
+ background-color: #FFD7D7
+}
+
+div.highlight .-Color[class*=-C225] {
+ color: #FFD7FF
+}
+
+div.highlight .-Color[class*=-BGC225] {
+ background-color: #FFD7FF
+}
+
+div.highlight .-Color[class*=-C226] {
+ color: #FFFF00
+}
+
+div.highlight .-Color[class*=-BGC226] {
+ background-color: #FFFF00
+}
+
+div.highlight .-Color[class*=-C227] {
+ color: #FFFF5F
+}
+
+div.highlight .-Color[class*=-BGC227] {
+ background-color: #FFFF5F
+}
+
+div.highlight .-Color[class*=-C228] {
+ color: #FFFF87
+}
+
+div.highlight .-Color[class*=-BGC228] {
+ background-color: #FFFF87
+}
+
+div.highlight .-Color[class*=-C229] {
+ color: #FFFFAF
+}
+
+div.highlight .-Color[class*=-BGC229] {
+ background-color: #FFFFAF
+}
+
+div.highlight .-Color[class*=-C230] {
+ color: #FFFFD7
+}
+
+div.highlight .-Color[class*=-BGC230] {
+ background-color: #FFFFD7
+}
+
+div.highlight .-Color[class*=-C231] {
+ color: #FFFFFF
+}
+
+div.highlight .-Color[class*=-BGC231] {
+ background-color: #FFFFFF
+}
+
+div.highlight .-Color[class*=-C232] {
+ color: #080808
+}
+
+div.highlight .-Color[class*=-BGC232] {
+ background-color: #080808
+}
+
+div.highlight .-Color[class*=-C233] {
+ color: #121212
+}
+
+div.highlight .-Color[class*=-BGC233] {
+ background-color: #121212
+}
+
+div.highlight .-Color[class*=-C234] {
+ color: #1C1C1C
+}
+
+div.highlight .-Color[class*=-BGC234] {
+ background-color: #1C1C1C
+}
+
+div.highlight .-Color[class*=-C235] {
+ color: #262626
+}
+
+div.highlight .-Color[class*=-BGC235] {
+ background-color: #262626
+}
+
+div.highlight .-Color[class*=-C236] {
+ color: #303030
+}
+
+div.highlight .-Color[class*=-BGC236] {
+ background-color: #303030
+}
+
+div.highlight .-Color[class*=-C237] {
+ color: #3A3A3A
+}
+
+div.highlight .-Color[class*=-BGC237] {
+ background-color: #3A3A3A
+}
+
+div.highlight .-Color[class*=-C238] {
+ color: #444444
+}
+
+div.highlight .-Color[class*=-BGC238] {
+ background-color: #444444
+}
+
+div.highlight .-Color[class*=-C239] {
+ color: #4E4E4E
+}
+
+div.highlight .-Color[class*=-BGC239] {
+ background-color: #4E4E4E
+}
+
+div.highlight .-Color[class*=-C240] {
+ color: #585858
+}
+
+div.highlight .-Color[class*=-BGC240] {
+ background-color: #585858
+}
+
+div.highlight .-Color[class*=-C241] {
+ color: #626262
+}
+
+div.highlight .-Color[class*=-BGC241] {
+ background-color: #626262
+}
+
+div.highlight .-Color[class*=-C242] {
+ color: #6C6C6C
+}
+
+div.highlight .-Color[class*=-BGC242] {
+ background-color: #6C6C6C
+}
+
+div.highlight .-Color[class*=-C243] {
+ color: #767676
+}
+
+div.highlight .-Color[class*=-BGC243] {
+ background-color: #767676
+}
+
+div.highlight .-Color[class*=-C244] {
+ color: #808080
+}
+
+div.highlight .-Color[class*=-BGC244] {
+ background-color: #808080
+}
+
+div.highlight .-Color[class*=-C245] {
+ color: #8A8A8A
+}
+
+div.highlight .-Color[class*=-BGC245] {
+ background-color: #8A8A8A
+}
+
+div.highlight .-Color[class*=-C246] {
+ color: #949494
+}
+
+div.highlight .-Color[class*=-BGC246] {
+ background-color: #949494
+}
+
+div.highlight .-Color[class*=-C247] {
+ color: #9E9E9E
+}
+
+div.highlight .-Color[class*=-BGC247] {
+ background-color: #9E9E9E
+}
+
+div.highlight .-Color[class*=-C248] {
+ color: #A8A8A8
+}
+
+div.highlight .-Color[class*=-BGC248] {
+ background-color: #A8A8A8
+}
+
+div.highlight .-Color[class*=-C249] {
+ color: #B2B2B2
+}
+
+div.highlight .-Color[class*=-BGC249] {
+ background-color: #B2B2B2
+}
+
+div.highlight .-Color[class*=-C250] {
+ color: #BCBCBC
+}
+
+div.highlight .-Color[class*=-BGC250] {
+ background-color: #BCBCBC
+}
+
+div.highlight .-Color[class*=-C251] {
+ color: #C6C6C6
+}
+
+div.highlight .-Color[class*=-BGC251] {
+ background-color: #C6C6C6
+}
+
+div.highlight .-Color[class*=-C252] {
+ color: #D0D0D0
+}
+
+div.highlight .-Color[class*=-BGC252] {
+ background-color: #D0D0D0
+}
+
+div.highlight .-Color[class*=-C253] {
+ color: #DADADA
+}
+
+div.highlight .-Color[class*=-BGC253] {
+ background-color: #DADADA
+}
+
+div.highlight .-Color[class*=-C254] {
+ color: #E4E4E4
+}
+
+div.highlight .-Color[class*=-BGC254] {
+ background-color: #E4E4E4
+}
+
+div.highlight .-Color[class*=-C255] {
+ color: #EEEEEE
+}
+
+div.highlight .-Color[class*=-BGC255] {
+ background-color: #EEEEEE
+}
diff --git a/branch/main/_static/plus.png b/branch/main/_static/plus.png
new file mode 100644
index 0000000..7107cec
Binary files /dev/null and b/branch/main/_static/plus.png differ
diff --git a/branch/main/_static/pygments.css b/branch/main/_static/pygments.css
new file mode 100644
index 0000000..6f8b210
--- /dev/null
+++ b/branch/main/_static/pygments.css
@@ -0,0 +1,75 @@
+pre { line-height: 125%; }
+td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
+span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
+td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
+span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
+.highlight .hll { background-color: #ffffcc }
+.highlight { background: #f8f8f8; }
+.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */
+.highlight .err { border: 1px solid #F00 } /* Error */
+.highlight .k { color: #008000; font-weight: bold } /* Keyword */
+.highlight .o { color: #666 } /* Operator */
+.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */
+.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */
+.highlight .cp { color: #9C6500 } /* Comment.Preproc */
+.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */
+.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */
+.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */
+.highlight .gd { color: #A00000 } /* Generic.Deleted */
+.highlight .ge { font-style: italic } /* Generic.Emph */
+.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
+.highlight .gr { color: #E40000 } /* Generic.Error */
+.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.highlight .gi { color: #008400 } /* Generic.Inserted */
+.highlight .go { color: #717171 } /* Generic.Output */
+.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
+.highlight .gs { font-weight: bold } /* Generic.Strong */
+.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.highlight .gt { color: #04D } /* Generic.Traceback */
+.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
+.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
+.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
+.highlight .kp { color: #008000 } /* Keyword.Pseudo */
+.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
+.highlight .kt { color: #B00040 } /* Keyword.Type */
+.highlight .m { color: #666 } /* Literal.Number */
+.highlight .s { color: #BA2121 } /* Literal.String */
+.highlight .na { color: #687822 } /* Name.Attribute */
+.highlight .nb { color: #008000 } /* Name.Builtin */
+.highlight .nc { color: #00F; font-weight: bold } /* Name.Class */
+.highlight .no { color: #800 } /* Name.Constant */
+.highlight .nd { color: #A2F } /* Name.Decorator */
+.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */
+.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */
+.highlight .nf { color: #00F } /* Name.Function */
+.highlight .nl { color: #767600 } /* Name.Label */
+.highlight .nn { color: #00F; font-weight: bold } /* Name.Namespace */
+.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */
+.highlight .nv { color: #19177C } /* Name.Variable */
+.highlight .ow { color: #A2F; font-weight: bold } /* Operator.Word */
+.highlight .w { color: #BBB } /* Text.Whitespace */
+.highlight .mb { color: #666 } /* Literal.Number.Bin */
+.highlight .mf { color: #666 } /* Literal.Number.Float */
+.highlight .mh { color: #666 } /* Literal.Number.Hex */
+.highlight .mi { color: #666 } /* Literal.Number.Integer */
+.highlight .mo { color: #666 } /* Literal.Number.Oct */
+.highlight .sa { color: #BA2121 } /* Literal.String.Affix */
+.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */
+.highlight .sc { color: #BA2121 } /* Literal.String.Char */
+.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */
+.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
+.highlight .s2 { color: #BA2121 } /* Literal.String.Double */
+.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */
+.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */
+.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */
+.highlight .sx { color: #008000 } /* Literal.String.Other */
+.highlight .sr { color: #A45A77 } /* Literal.String.Regex */
+.highlight .s1 { color: #BA2121 } /* Literal.String.Single */
+.highlight .ss { color: #19177C } /* Literal.String.Symbol */
+.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */
+.highlight .fm { color: #00F } /* Name.Function.Magic */
+.highlight .vc { color: #19177C } /* Name.Variable.Class */
+.highlight .vg { color: #19177C } /* Name.Variable.Global */
+.highlight .vi { color: #19177C } /* Name.Variable.Instance */
+.highlight .vm { color: #19177C } /* Name.Variable.Magic */
+.highlight .il { color: #666 } /* Literal.Number.Integer.Long */
\ No newline at end of file
diff --git a/branch/main/_static/searchtools.js b/branch/main/_static/searchtools.js
new file mode 100644
index 0000000..b08d58c
--- /dev/null
+++ b/branch/main/_static/searchtools.js
@@ -0,0 +1,620 @@
+/*
+ * searchtools.js
+ * ~~~~~~~~~~~~~~~~
+ *
+ * Sphinx JavaScript utilities for the full-text search.
+ *
+ * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+"use strict";
+
+/**
+ * Simple result scoring code.
+ */
+if (typeof Scorer === "undefined") {
+ var Scorer = {
+ // Implement the following function to further tweak the score for each result
+ // The function takes a result array [docname, title, anchor, descr, score, filename]
+ // and returns the new score.
+ /*
+ score: result => {
+ const [docname, title, anchor, descr, score, filename] = result
+ return score
+ },
+ */
+
+ // query matches the full name of an object
+ objNameMatch: 11,
+ // or matches in the last dotted part of the object name
+ objPartialMatch: 6,
+ // Additive scores depending on the priority of the object
+ objPrio: {
+ 0: 15, // used to be importantResults
+ 1: 5, // used to be objectResults
+ 2: -5, // used to be unimportantResults
+ },
+ // Used when the priority is not in the mapping.
+ objPrioDefault: 0,
+
+ // query found in title
+ title: 15,
+ partialTitle: 7,
+ // query found in terms
+ term: 5,
+ partialTerm: 2,
+ };
+}
+
+const _removeChildren = (element) => {
+ while (element && element.lastChild) element.removeChild(element.lastChild);
+};
+
+/**
+ * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
+ */
+const _escapeRegExp = (string) =>
+ string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
+
+const _displayItem = (item, searchTerms, highlightTerms) => {
+ const docBuilder = DOCUMENTATION_OPTIONS.BUILDER;
+ const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX;
+ const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX;
+ const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY;
+ const contentRoot = document.documentElement.dataset.content_root;
+
+ const [docName, title, anchor, descr, score, _filename] = item;
+
+ let listItem = document.createElement("li");
+ let requestUrl;
+ let linkUrl;
+ if (docBuilder === "dirhtml") {
+ // dirhtml builder
+ let dirname = docName + "/";
+ if (dirname.match(/\/index\/$/))
+ dirname = dirname.substring(0, dirname.length - 6);
+ else if (dirname === "index/") dirname = "";
+ requestUrl = contentRoot + dirname;
+ linkUrl = requestUrl;
+ } else {
+ // normal html builders
+ requestUrl = contentRoot + docName + docFileSuffix;
+ linkUrl = docName + docLinkSuffix;
+ }
+ let linkEl = listItem.appendChild(document.createElement("a"));
+ linkEl.href = linkUrl + anchor;
+ linkEl.dataset.score = score;
+ linkEl.innerHTML = title;
+ if (descr) {
+ listItem.appendChild(document.createElement("span")).innerHTML =
+ " (" + descr + ")";
+ // highlight search terms in the description
+ if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
+ highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
+ }
+ else if (showSearchSummary)
+ fetch(requestUrl)
+ .then((responseData) => responseData.text())
+ .then((data) => {
+ if (data)
+ listItem.appendChild(
+ Search.makeSearchSummary(data, searchTerms, anchor)
+ );
+ // highlight search terms in the summary
+ if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
+ highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
+ });
+ Search.output.appendChild(listItem);
+};
+const _finishSearch = (resultCount) => {
+ Search.stopPulse();
+ Search.title.innerText = _("Search Results");
+ if (!resultCount)
+ Search.status.innerText = Documentation.gettext(
+ "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories."
+ );
+ else
+ Search.status.innerText = _(
+ "Search finished, found ${resultCount} page(s) matching the search query."
+ ).replace('${resultCount}', resultCount);
+};
+const _displayNextItem = (
+ results,
+ resultCount,
+ searchTerms,
+ highlightTerms,
+) => {
+ // results left, load the summary and display it
+ // this is intended to be dynamic (don't sub resultsCount)
+ if (results.length) {
+ _displayItem(results.pop(), searchTerms, highlightTerms);
+ setTimeout(
+ () => _displayNextItem(results, resultCount, searchTerms, highlightTerms),
+ 5
+ );
+ }
+ // search finished, update title and status message
+ else _finishSearch(resultCount);
+};
+// Helper function used by query() to order search results.
+// Each input is an array of [docname, title, anchor, descr, score, filename].
+// Order the results by score (in opposite order of appearance, since the
+// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically.
+const _orderResultsByScoreThenName = (a, b) => {
+ const leftScore = a[4];
+ const rightScore = b[4];
+ if (leftScore === rightScore) {
+ // same score: sort alphabetically
+ const leftTitle = a[1].toLowerCase();
+ const rightTitle = b[1].toLowerCase();
+ if (leftTitle === rightTitle) return 0;
+ return leftTitle > rightTitle ? -1 : 1; // inverted is intentional
+ }
+ return leftScore > rightScore ? 1 : -1;
+};
+
+/**
+ * Default splitQuery function. Can be overridden in ``sphinx.search`` with a
+ * custom function per language.
+ *
+ * The regular expression works by splitting the string on consecutive characters
+ * that are not Unicode letters, numbers, underscores, or emoji characters.
+ * This is the same as ``\W+`` in Python, preserving the surrogate pair area.
+ */
+if (typeof splitQuery === "undefined") {
+ var splitQuery = (query) => query
+ .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu)
+ .filter(term => term) // remove remaining empty strings
+}
+
+/**
+ * Search Module
+ */
+const Search = {
+ _index: null,
+ _queued_query: null,
+ _pulse_status: -1,
+
+ htmlToText: (htmlString, anchor) => {
+ const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html');
+ for (const removalQuery of [".headerlink", "script", "style"]) {
+ htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() });
+ }
+ if (anchor) {
+ const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`);
+ if (anchorContent) return anchorContent.textContent;
+
+ console.warn(
+ `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.`
+ );
+ }
+
+ // if anchor not specified or not found, fall back to main content
+ const docContent = htmlElement.querySelector('[role="main"]');
+ if (docContent) return docContent.textContent;
+
+ console.warn(
+ "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template."
+ );
+ return "";
+ },
+
+ init: () => {
+ const query = new URLSearchParams(window.location.search).get("q");
+ document
+ .querySelectorAll('input[name="q"]')
+ .forEach((el) => (el.value = query));
+ if (query) Search.performSearch(query);
+ },
+
+ loadIndex: (url) =>
+ (document.body.appendChild(document.createElement("script")).src = url),
+
+ setIndex: (index) => {
+ Search._index = index;
+ if (Search._queued_query !== null) {
+ const query = Search._queued_query;
+ Search._queued_query = null;
+ Search.query(query);
+ }
+ },
+
+ hasIndex: () => Search._index !== null,
+
+ deferQuery: (query) => (Search._queued_query = query),
+
+ stopPulse: () => (Search._pulse_status = -1),
+
+ startPulse: () => {
+ if (Search._pulse_status >= 0) return;
+
+ const pulse = () => {
+ Search._pulse_status = (Search._pulse_status + 1) % 4;
+ Search.dots.innerText = ".".repeat(Search._pulse_status);
+ if (Search._pulse_status >= 0) window.setTimeout(pulse, 500);
+ };
+ pulse();
+ },
+
+ /**
+ * perform a search for something (or wait until index is loaded)
+ */
+ performSearch: (query) => {
+ // create the required interface elements
+ const searchText = document.createElement("h2");
+ searchText.textContent = _("Searching");
+ const searchSummary = document.createElement("p");
+ searchSummary.classList.add("search-summary");
+ searchSummary.innerText = "";
+ const searchList = document.createElement("ul");
+ searchList.classList.add("search");
+
+ const out = document.getElementById("search-results");
+ Search.title = out.appendChild(searchText);
+ Search.dots = Search.title.appendChild(document.createElement("span"));
+ Search.status = out.appendChild(searchSummary);
+ Search.output = out.appendChild(searchList);
+
+ const searchProgress = document.getElementById("search-progress");
+ // Some themes don't use the search progress node
+ if (searchProgress) {
+ searchProgress.innerText = _("Preparing search...");
+ }
+ Search.startPulse();
+
+ // index already loaded, the browser was quick!
+ if (Search.hasIndex()) Search.query(query);
+ else Search.deferQuery(query);
+ },
+
+ _parseQuery: (query) => {
+ // stem the search terms and add them to the correct list
+ const stemmer = new Stemmer();
+ const searchTerms = new Set();
+ const excludedTerms = new Set();
+ const highlightTerms = new Set();
+ const objectTerms = new Set(splitQuery(query.toLowerCase().trim()));
+ splitQuery(query.trim()).forEach((queryTerm) => {
+ const queryTermLower = queryTerm.toLowerCase();
+
+ // maybe skip this "word"
+ // stopwords array is from language_data.js
+ if (
+ stopwords.indexOf(queryTermLower) !== -1 ||
+ queryTerm.match(/^\d+$/)
+ )
+ return;
+
+ // stem the word
+ let word = stemmer.stemWord(queryTermLower);
+ // select the correct list
+ if (word[0] === "-") excludedTerms.add(word.substr(1));
+ else {
+ searchTerms.add(word);
+ highlightTerms.add(queryTermLower);
+ }
+ });
+
+ if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js
+ localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" "))
+ }
+
+ // console.debug("SEARCH: searching for:");
+ // console.info("required: ", [...searchTerms]);
+ // console.info("excluded: ", [...excludedTerms]);
+
+ return [query, searchTerms, excludedTerms, highlightTerms, objectTerms];
+ },
+
+ /**
+ * execute search (requires search index to be loaded)
+ */
+ _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => {
+ const filenames = Search._index.filenames;
+ const docNames = Search._index.docnames;
+ const titles = Search._index.titles;
+ const allTitles = Search._index.alltitles;
+ const indexEntries = Search._index.indexentries;
+
+ // Collect multiple result groups to be sorted separately and then ordered.
+ // Each is an array of [docname, title, anchor, descr, score, filename].
+ const normalResults = [];
+ const nonMainIndexResults = [];
+
+ _removeChildren(document.getElementById("search-progress"));
+
+ const queryLower = query.toLowerCase().trim();
+ for (const [title, foundTitles] of Object.entries(allTitles)) {
+ if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) {
+ for (const [file, id] of foundTitles) {
+ const score = Math.round(Scorer.title * queryLower.length / title.length);
+ const boost = titles[file] === title ? 1 : 0; // add a boost for document titles
+ normalResults.push([
+ docNames[file],
+ titles[file] !== title ? `${titles[file]} > ${title}` : title,
+ id !== null ? "#" + id : "",
+ null,
+ score + boost,
+ filenames[file],
+ ]);
+ }
+ }
+ }
+
+ // search for explicit entries in index directives
+ for (const [entry, foundEntries] of Object.entries(indexEntries)) {
+ if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) {
+ for (const [file, id, isMain] of foundEntries) {
+ const score = Math.round(100 * queryLower.length / entry.length);
+ const result = [
+ docNames[file],
+ titles[file],
+ id ? "#" + id : "",
+ null,
+ score,
+ filenames[file],
+ ];
+ if (isMain) {
+ normalResults.push(result);
+ } else {
+ nonMainIndexResults.push(result);
+ }
+ }
+ }
+ }
+
+ // lookup as object
+ objectTerms.forEach((term) =>
+ normalResults.push(...Search.performObjectSearch(term, objectTerms))
+ );
+
+ // lookup as search terms in fulltext
+ normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms));
+
+ // let the scorer override scores with a custom scoring function
+ if (Scorer.score) {
+ normalResults.forEach((item) => (item[4] = Scorer.score(item)));
+ nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item)));
+ }
+
+ // Sort each group of results by score and then alphabetically by name.
+ normalResults.sort(_orderResultsByScoreThenName);
+ nonMainIndexResults.sort(_orderResultsByScoreThenName);
+
+ // Combine the result groups in (reverse) order.
+ // Non-main index entries are typically arbitrary cross-references,
+ // so display them after other results.
+ let results = [...nonMainIndexResults, ...normalResults];
+
+ // remove duplicate search results
+ // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept
+ let seen = new Set();
+ results = results.reverse().reduce((acc, result) => {
+ let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(',');
+ if (!seen.has(resultStr)) {
+ acc.push(result);
+ seen.add(resultStr);
+ }
+ return acc;
+ }, []);
+
+ return results.reverse();
+ },
+
+ query: (query) => {
+ const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query);
+ const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms);
+
+ // for debugging
+ //Search.lastresults = results.slice(); // a copy
+ // console.info("search results:", Search.lastresults);
+
+ // print the results
+ _displayNextItem(results, results.length, searchTerms, highlightTerms);
+ },
+
+ /**
+ * search for object names
+ */
+ performObjectSearch: (object, objectTerms) => {
+ const filenames = Search._index.filenames;
+ const docNames = Search._index.docnames;
+ const objects = Search._index.objects;
+ const objNames = Search._index.objnames;
+ const titles = Search._index.titles;
+
+ const results = [];
+
+ const objectSearchCallback = (prefix, match) => {
+ const name = match[4]
+ const fullname = (prefix ? prefix + "." : "") + name;
+ const fullnameLower = fullname.toLowerCase();
+ if (fullnameLower.indexOf(object) < 0) return;
+
+ let score = 0;
+ const parts = fullnameLower.split(".");
+
+ // check for different match types: exact matches of full name or
+ // "last name" (i.e. last dotted part)
+ if (fullnameLower === object || parts.slice(-1)[0] === object)
+ score += Scorer.objNameMatch;
+ else if (parts.slice(-1)[0].indexOf(object) > -1)
+ score += Scorer.objPartialMatch; // matches in last name
+
+ const objName = objNames[match[1]][2];
+ const title = titles[match[0]];
+
+ // If more than one term searched for, we require other words to be
+ // found in the name/title/description
+ const otherTerms = new Set(objectTerms);
+ otherTerms.delete(object);
+ if (otherTerms.size > 0) {
+ const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase();
+ if (
+ [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0)
+ )
+ return;
+ }
+
+ let anchor = match[3];
+ if (anchor === "") anchor = fullname;
+ else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname;
+
+ const descr = objName + _(", in ") + title;
+
+ // add custom score for some objects according to scorer
+ if (Scorer.objPrio.hasOwnProperty(match[2]))
+ score += Scorer.objPrio[match[2]];
+ else score += Scorer.objPrioDefault;
+
+ results.push([
+ docNames[match[0]],
+ fullname,
+ "#" + anchor,
+ descr,
+ score,
+ filenames[match[0]],
+ ]);
+ };
+ Object.keys(objects).forEach((prefix) =>
+ objects[prefix].forEach((array) =>
+ objectSearchCallback(prefix, array)
+ )
+ );
+ return results;
+ },
+
+ /**
+ * search for full-text terms in the index
+ */
+ performTermsSearch: (searchTerms, excludedTerms) => {
+ // prepare search
+ const terms = Search._index.terms;
+ const titleTerms = Search._index.titleterms;
+ const filenames = Search._index.filenames;
+ const docNames = Search._index.docnames;
+ const titles = Search._index.titles;
+
+ const scoreMap = new Map();
+ const fileMap = new Map();
+
+ // perform the search on the required terms
+ searchTerms.forEach((word) => {
+ const files = [];
+ const arr = [
+ { files: terms[word], score: Scorer.term },
+ { files: titleTerms[word], score: Scorer.title },
+ ];
+ // add support for partial matches
+ if (word.length > 2) {
+ const escapedWord = _escapeRegExp(word);
+ if (!terms.hasOwnProperty(word)) {
+ Object.keys(terms).forEach((term) => {
+ if (term.match(escapedWord))
+ arr.push({ files: terms[term], score: Scorer.partialTerm });
+ });
+ }
+ if (!titleTerms.hasOwnProperty(word)) {
+ Object.keys(titleTerms).forEach((term) => {
+ if (term.match(escapedWord))
+ arr.push({ files: titleTerms[term], score: Scorer.partialTitle });
+ });
+ }
+ }
+
+ // no match but word was a required one
+ if (arr.every((record) => record.files === undefined)) return;
+
+ // found search word in contents
+ arr.forEach((record) => {
+ if (record.files === undefined) return;
+
+ let recordFiles = record.files;
+ if (recordFiles.length === undefined) recordFiles = [recordFiles];
+ files.push(...recordFiles);
+
+ // set score for the word in each file
+ recordFiles.forEach((file) => {
+ if (!scoreMap.has(file)) scoreMap.set(file, {});
+ scoreMap.get(file)[word] = record.score;
+ });
+ });
+
+ // create the mapping
+ files.forEach((file) => {
+ if (!fileMap.has(file)) fileMap.set(file, [word]);
+ else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word);
+ });
+ });
+
+ // now check if the files don't contain excluded terms
+ const results = [];
+ for (const [file, wordList] of fileMap) {
+ // check if all requirements are matched
+
+ // as search terms with length < 3 are discarded
+ const filteredTermCount = [...searchTerms].filter(
+ (term) => term.length > 2
+ ).length;
+ if (
+ wordList.length !== searchTerms.size &&
+ wordList.length !== filteredTermCount
+ )
+ continue;
+
+ // ensure that none of the excluded terms is in the search result
+ if (
+ [...excludedTerms].some(
+ (term) =>
+ terms[term] === file ||
+ titleTerms[term] === file ||
+ (terms[term] || []).includes(file) ||
+ (titleTerms[term] || []).includes(file)
+ )
+ )
+ break;
+
+ // select one (max) score for the file.
+ const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w]));
+ // add result to the result list
+ results.push([
+ docNames[file],
+ titles[file],
+ "",
+ null,
+ score,
+ filenames[file],
+ ]);
+ }
+ return results;
+ },
+
+ /**
+ * helper function to return a node containing the
+ * search summary for a given text. keywords is a list
+ * of stemmed words.
+ */
+ makeSearchSummary: (htmlText, keywords, anchor) => {
+ const text = Search.htmlToText(htmlText, anchor);
+ if (text === "") return null;
+
+ const textLower = text.toLowerCase();
+ const actualStartPosition = [...keywords]
+ .map((k) => textLower.indexOf(k.toLowerCase()))
+ .filter((i) => i > -1)
+ .slice(-1)[0];
+ const startWithContext = Math.max(actualStartPosition - 120, 0);
+
+ const top = startWithContext === 0 ? "" : "...";
+ const tail = startWithContext + 240 < text.length ? "..." : "";
+
+ let summary = document.createElement("p");
+ summary.classList.add("context");
+ summary.textContent = top + text.substr(startWithContext, 240).trim() + tail;
+
+ return summary;
+ },
+};
+
+_ready(Search.init);
diff --git a/branch/main/_static/sphinx_highlight.js b/branch/main/_static/sphinx_highlight.js
new file mode 100644
index 0000000..8a96c69
--- /dev/null
+++ b/branch/main/_static/sphinx_highlight.js
@@ -0,0 +1,154 @@
+/* Highlighting utilities for Sphinx HTML documentation. */
+"use strict";
+
+const SPHINX_HIGHLIGHT_ENABLED = true
+
+/**
+ * highlight a given string on a node by wrapping it in
+ * span elements with the given class name.
+ */
+const _highlight = (node, addItems, text, className) => {
+ if (node.nodeType === Node.TEXT_NODE) {
+ const val = node.nodeValue;
+ const parent = node.parentNode;
+ const pos = val.toLowerCase().indexOf(text);
+ if (
+ pos >= 0 &&
+ !parent.classList.contains(className) &&
+ !parent.classList.contains("nohighlight")
+ ) {
+ let span;
+
+ const closestNode = parent.closest("body, svg, foreignObject");
+ const isInSVG = closestNode && closestNode.matches("svg");
+ if (isInSVG) {
+ span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
+ } else {
+ span = document.createElement("span");
+ span.classList.add(className);
+ }
+
+ span.appendChild(document.createTextNode(val.substr(pos, text.length)));
+ const rest = document.createTextNode(val.substr(pos + text.length));
+ parent.insertBefore(
+ span,
+ parent.insertBefore(
+ rest,
+ node.nextSibling
+ )
+ );
+ node.nodeValue = val.substr(0, pos);
+ /* There may be more occurrences of search term in this node. So call this
+ * function recursively on the remaining fragment.
+ */
+ _highlight(rest, addItems, text, className);
+
+ if (isInSVG) {
+ const rect = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "rect"
+ );
+ const bbox = parent.getBBox();
+ rect.x.baseVal.value = bbox.x;
+ rect.y.baseVal.value = bbox.y;
+ rect.width.baseVal.value = bbox.width;
+ rect.height.baseVal.value = bbox.height;
+ rect.setAttribute("class", className);
+ addItems.push({ parent: parent, target: rect });
+ }
+ }
+ } else if (node.matches && !node.matches("button, select, textarea")) {
+ node.childNodes.forEach((el) => _highlight(el, addItems, text, className));
+ }
+};
+const _highlightText = (thisNode, text, className) => {
+ let addItems = [];
+ _highlight(thisNode, addItems, text, className);
+ addItems.forEach((obj) =>
+ obj.parent.insertAdjacentElement("beforebegin", obj.target)
+ );
+};
+
+/**
+ * Small JavaScript module for the documentation.
+ */
+const SphinxHighlight = {
+
+ /**
+ * highlight the search words provided in localstorage in the text
+ */
+ highlightSearchWords: () => {
+ if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight
+
+ // get and clear terms from localstorage
+ const url = new URL(window.location);
+ const highlight =
+ localStorage.getItem("sphinx_highlight_terms")
+ || url.searchParams.get("highlight")
+ || "";
+ localStorage.removeItem("sphinx_highlight_terms")
+ url.searchParams.delete("highlight");
+ window.history.replaceState({}, "", url);
+
+ // get individual terms from highlight string
+ const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
+ if (terms.length === 0) return; // nothing to do
+
+ // There should never be more than one element matching "div.body"
+ const divBody = document.querySelectorAll("div.body");
+ const body = divBody.length ? divBody[0] : document.querySelector("body");
+ window.setTimeout(() => {
+ terms.forEach((term) => _highlightText(body, term, "highlighted"));
+ }, 10);
+
+ const searchBox = document.getElementById("searchbox");
+ if (searchBox === null) return;
+ searchBox.appendChild(
+ document
+ .createRange()
+ .createContextualFragment(
+ '' +
+ '' +
+ _("Hide Search Matches") +
+ "
"
+ )
+ );
+ },
+
+ /**
+ * helper function to hide the search marks again
+ */
+ hideSearchWords: () => {
+ document
+ .querySelectorAll("#searchbox .highlight-link")
+ .forEach((el) => el.remove());
+ document
+ .querySelectorAll("span.highlighted")
+ .forEach((el) => el.classList.remove("highlighted"));
+ localStorage.removeItem("sphinx_highlight_terms")
+ },
+
+ initEscapeListener: () => {
+ // only install a listener if it is really needed
+ if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return;
+
+ document.addEventListener("keydown", (event) => {
+ // bail for input elements
+ if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
+ // bail with special keys
+ if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;
+ if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) {
+ SphinxHighlight.hideSearchWords();
+ event.preventDefault();
+ }
+ });
+ },
+};
+
+_ready(() => {
+ /* Do not call highlightSearchWords() when we are on the search page.
+ * It will highlight words from the *previous* search query.
+ */
+ if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords();
+ SphinxHighlight.initEscapeListener();
+});
diff --git a/branch/main/_static/sphinx_lesson.css b/branch/main/_static/sphinx_lesson.css
new file mode 100644
index 0000000..68cb32d
--- /dev/null
+++ b/branch/main/_static/sphinx_lesson.css
@@ -0,0 +1,103 @@
+/* sphinx_lesson.css
+ * https://webaim.org/resources/contrastchecker/?fcolor=00000&bcolor=FCE762
+ * */
+:root {
+ --sphinx-lesson-selection-bg-color: #fce762;
+ --sphinx-lesson-selection-fg-color: #000000;
+}
+
+/* https://webaim.org/resources/contrastchecker/?fcolor=FFFFFF&bcolor=745315
+ * when dark theme is selected the some themes use this attirbute
+ */
+html[data-theme='dark'], body[data-theme='dark'] {
+ --sphinx-lesson-selection-bg-color: #745315;
+ --sphinx-lesson-selection-fg-color: #ffffff;
+}
+
+/* when browser/system theme is dark and no theme is selected */
+@media (prefers-color-scheme: dark) {
+ html[data-theme='auto'], body[data-theme='auto'] {
+ --sphinx-lesson-selection-bg-color: #745315;
+ --sphinx-lesson-selection-fg-color: #ffffff;
+ }
+}
+
+body.wy-body-for-nav img.with-border {
+ border: 2px solid;
+}
+
+.rst-content .admonition-no-content {
+ padding-bottom: 0px;
+}
+
+.rst-content .demo > .admonition-title::before {
+ content: "\01F440"; /* Eyes */ }
+.rst-content .type-along > .admonition-title::before {
+ content: "\02328\0FE0F"; /* Keyboard */ }
+.rst-content .exercise > .admonition-title::before {
+ content: "\0270D\0FE0F"; /* Hand */ }
+.rst-content .solution > .admonition-title::before {
+ content: "\02714\0FE0E"; /* Check mark */ }
+.rst-content .homework > .admonition-title::before {
+ content: "\01F4DD"; /* Memo */ }
+.rst-content .discussion > .admonition-title::before {
+ content: "\01F4AC"; /* Speech balloon */ }
+.rst-content .questions > .admonition-title::before {
+ content: "\02753\0FE0E"; /* Question mark */ }
+.rst-content .prerequisites > .admonition-title::before {
+ content: "\02699"; /* Gear */ }
+.rst-content .seealso > .admonition-title::before {
+ content: "\027A1\0FE0E"; /* Question mark */ }
+
+
+/* instructor-note */
+.rst-content .instructor-note {
+ background: #e7e7e7;
+}
+.rst-content .instructor-note > .admonition-title {
+ background: #6a6a6a;
+}
+.rst-content .instructor-note > .admonition-title::before {
+ content: "";
+}
+
+
+/* sphinx_toggle_button, make the font white */
+.rst-content .toggle.admonition button.toggle-button {
+ color: white;
+}
+
+/* sphinx-togglebutton, remove underflow when toggled to hidden mode */
+.rst-content .admonition.toggle-hidden {
+ padding-bottom: 0px;
+}
+
+/* selection / highlight colour uses a yellow background and a black text */
+/*** Works on common browsers ***/
+::selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
+
+/*** Mozilla based browsers ***/
+::-moz-selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
+
+/***For Other Browsers ***/
+::-o-selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
+
+::-ms-selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
+
+/*** For Webkit ***/
+::-webkit-selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
diff --git a/branch/main/_static/sphinx_rtd_theme_ext_color_contrast.css b/branch/main/_static/sphinx_rtd_theme_ext_color_contrast.css
new file mode 100644
index 0000000..e68feb8
--- /dev/null
+++ b/branch/main/_static/sphinx_rtd_theme_ext_color_contrast.css
@@ -0,0 +1,47 @@
+/* The following are for web accessibility of sphinx_rtd_theme: they
+ * solve some of the most frequent contrast issues. Remove when this
+ * solved:
+ * https://github.com/readthedocs/sphinx_rtd_theme/issues/971
+ */
+/* background: #fcfcfc, note boxes #E7F2FA */
+a { color: #2573A7; } /* original #2980B9, #1F5C84; */
+body { color: #242424; } /* original #404040, #383838 */
+.wy-side-nav-search>a, .wy-side-nav-search .wy-dropdown>a {
+ color: #ffffff;
+} /* original #fcfcfc */
+footer { color: #737373; } /* original gray=#808080*/
+footer span.commit code, footer span.commit .rst-content tt, .rst-content footer span.commit tt {
+ color: #737373;
+} /* original gray=#808080*/
+.rst-content tt.literal, .rst-content tt.literal, .rst-content code.literal {
+ color: #AB2314;
+}
+/* Sidebar background */
+.wy-side-nav-search { background-color: #277CB4;}
+
+/* Same, but for pygments */
+.highlight .ch { color: #3E7A89; } /* #! line */
+.highlight .c1 { color: #3E7A89; } /* also comments */
+.highlight .nv { color: #AD3ECC; } /* variable */
+.highlight .gp { color: #B45608; } /* prompt character, $*/
+.highlight .si { color: #3975B1; } /* ${} variable text */
+.highlight .nc { color: #0C78A7; }
+
+/* Sphinx admonitions */
+/* warning */
+.wy-alert.wy-alert-warning .wy-alert-title, .rst-content .wy-alert-warning.note .wy-alert-title, .rst-content .attention .wy-alert-title, .rst-content .caution .wy-alert-title, .rst-content .wy-alert-warning.danger .wy-alert-title, .rst-content .wy-alert-warning.error .wy-alert-title, .rst-content .wy-alert-warning.hint .wy-alert-title, .rst-content .wy-alert-warning.important .wy-alert-title, .rst-content .wy-alert-warning.tip .wy-alert-title, .rst-content .warning .wy-alert-title, .rst-content .wy-alert-warning.seealso .wy-alert-title, .rst-content .admonition-todo .wy-alert-title, .rst-content .wy-alert-warning.admonition .wy-alert-title, .wy-alert.wy-alert-warning .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-warning .admonition-title, .rst-content .wy-alert-warning.note .admonition-title, .rst-content .attention .admonition-title, .rst-content .caution .admonition-title, .rst-content .wy-alert-warning.danger .admonition-title, .rst-content .wy-alert-warning.error .admonition-title, .rst-content .wy-alert-warning.hint .admonition-title, .rst-content .wy-alert-warning.important .admonition-title, .rst-content .wy-alert-warning.tip .admonition-title, .rst-content .warning .admonition-title, .rst-content .wy-alert-warning.seealso .admonition-title, .rst-content .admonition-todo .admonition-title, .rst-content .wy-alert-warning.admonition .admonition-title {
+ background: #B15E16; }
+/* important */
+.wy-alert.wy-alert-success .wy-alert-title, .rst-content .wy-alert-success.note .wy-alert-title, .rst-content .wy-alert-success.attention .wy-alert-title, .rst-content .wy-alert-success.caution .wy-alert-title, .rst-content .wy-alert-success.danger .wy-alert-title, .rst-content .wy-alert-success.error .wy-alert-title, .rst-content .hint .wy-alert-title, .rst-content .important .wy-alert-title, .rst-content .tip .wy-alert-title, .rst-content .wy-alert-success.warning .wy-alert-title, .rst-content .wy-alert-success.seealso .wy-alert-title, .rst-content .wy-alert-success.admonition-todo .wy-alert-title, .rst-content .wy-alert-success.admonition .wy-alert-title, .wy-alert.wy-alert-success .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-success .admonition-title, .rst-content .wy-alert-success.note .admonition-title, .rst-content .wy-alert-success.attention .admonition-title, .rst-content .wy-alert-success.caution .admonition-title, .rst-content .wy-alert-success.danger .admonition-title, .rst-content .wy-alert-success.error .admonition-title, .rst-content .hint .admonition-title, .rst-content .important .admonition-title, .rst-content .tip .admonition-title, .rst-content .wy-alert-success.warning .admonition-title, .rst-content .wy-alert-success.seealso .admonition-title, .rst-content .wy-alert-success.admonition-todo .admonition-title, .rst-content .wy-alert-success.admonition .admonition-title {
+ background: #12826C; }
+/* seealso, note, etc */
+.wy-alert.wy-alert-info .wy-alert-title, .rst-content .note .wy-alert-title, .rst-content .wy-alert-info.attention .wy-alert-title, .rst-content .wy-alert-info.caution .wy-alert-title, .rst-content .wy-alert-info.danger .wy-alert-title, .rst-content .wy-alert-info.error .wy-alert-title, .rst-content .wy-alert-info.hint .wy-alert-title, .rst-content .wy-alert-info.important .wy-alert-title, .rst-content .wy-alert-info.tip .wy-alert-title, .rst-content .wy-alert-info.warning .wy-alert-title, .rst-content .seealso .wy-alert-title, .rst-content .wy-alert-info.admonition-todo .wy-alert-title, .rst-content .wy-alert-info.admonition .wy-alert-title, .wy-alert.wy-alert-info .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-info .admonition-title, .rst-content .note .admonition-title, .rst-content .wy-alert-info.attention .admonition-title, .rst-content .wy-alert-info.caution .admonition-title, .rst-content .wy-alert-info.danger .admonition-title, .rst-content .wy-alert-info.error .admonition-title, .rst-content .wy-alert-info.hint .admonition-title, .rst-content .wy-alert-info.important .admonition-title, .rst-content .wy-alert-info.tip .admonition-title, .rst-content .wy-alert-info.warning .admonition-title, .rst-content .seealso .admonition-title, .rst-content .wy-alert-info.admonition-todo .admonition-title, .rst-content .wy-alert-info.admonition .admonition-title {
+ background: #277CB4; }
+/* error, danger */
+.rst-content .danger .admonition-title, .rst-content .danger .wy-alert-title, .rst-content .error .admonition-title, .rst-content .error .wy-alert-title, .rst-content .wy-alert-danger.admonition-todo .admonition-title, .rst-content .wy-alert-danger.admonition-todo .wy-alert-title, .rst-content .wy-alert-danger.admonition .admonition-title, .rst-content .wy-alert-danger.admonition .wy-alert-title, .rst-content .wy-alert-danger.attention .admonition-title, .rst-content .wy-alert-danger.attention .wy-alert-title, .rst-content .wy-alert-danger.caution .admonition-title, .rst-content .wy-alert-danger.caution .wy-alert-title, .rst-content .wy-alert-danger.hint .admonition-title, .rst-content .wy-alert-danger.hint .wy-alert-title, .rst-content .wy-alert-danger.important .admonition-title, .rst-content .wy-alert-danger.important .wy-alert-title, .rst-content .wy-alert-danger.note .admonition-title, .rst-content .wy-alert-danger.note .wy-alert-title, .rst-content .wy-alert-danger.seealso .admonition-title, .rst-content .wy-alert-danger.seealso .wy-alert-title, .rst-content .wy-alert-danger.tip .admonition-title, .rst-content .wy-alert-danger.tip .wy-alert-title, .rst-content .wy-alert-danger.warning .admonition-title, .rst-content .wy-alert-danger.warning .wy-alert-title, .rst-content .wy-alert.wy-alert-danger .admonition-title, .wy-alert.wy-alert-danger .rst-content .admonition-title, .wy-alert.wy-alert-danger .wy-alert-title {
+ background: #e31704;
+}
+
+/* Generic admonition titles */
+.wy-alert-title, .rst-content .admonition-title {
+ background: #277CB4; }
diff --git a/branch/main/_static/style.css b/branch/main/_static/style.css
new file mode 100644
index 0000000..59e60ee
--- /dev/null
+++ b/branch/main/_static/style.css
@@ -0,0 +1,6 @@
+.rst-content .objectives {
+ background: #fee0d2;
+}
+.rst-content .objectives > .admonition-title {
+ background: #fc9272;
+}
diff --git a/branch/main/_static/tabs.css b/branch/main/_static/tabs.css
new file mode 100644
index 0000000..957ba60
--- /dev/null
+++ b/branch/main/_static/tabs.css
@@ -0,0 +1,89 @@
+.sphinx-tabs {
+ margin-bottom: 1rem;
+}
+
+[role="tablist"] {
+ border-bottom: 1px solid #a0b3bf;
+}
+
+.sphinx-tabs-tab {
+ position: relative;
+ font-family: Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;
+ color: #1D5C87;
+ line-height: 24px;
+ margin: 0;
+ font-size: 16px;
+ font-weight: 400;
+ background-color: rgba(255, 255, 255, 0);
+ border-radius: 5px 5px 0 0;
+ border: 0;
+ padding: 1rem 1.5rem;
+ margin-bottom: 0;
+}
+
+.sphinx-tabs-tab[aria-selected="true"] {
+ font-weight: 700;
+ border: 1px solid #a0b3bf;
+ border-bottom: 1px solid white;
+ margin: -1px;
+ background-color: white;
+}
+
+.sphinx-tabs-tab:focus {
+ z-index: 1;
+ outline-offset: 1px;
+}
+
+.sphinx-tabs-panel {
+ position: relative;
+ padding: 1rem;
+ border: 1px solid #a0b3bf;
+ margin: 0px -1px -1px -1px;
+ border-radius: 0 0 5px 5px;
+ border-top: 0;
+ background: white;
+}
+
+.sphinx-tabs-panel.code-tab {
+ padding: 0.4rem;
+}
+
+.sphinx-tab img {
+ margin-bottom: 24 px;
+}
+
+/* Dark theme preference styling */
+
+@media (prefers-color-scheme: dark) {
+ body[data-theme="auto"] .sphinx-tabs-panel {
+ color: white;
+ background-color: rgb(50, 50, 50);
+ }
+
+ body[data-theme="auto"] .sphinx-tabs-tab {
+ color: white;
+ background-color: rgba(255, 255, 255, 0.05);
+ }
+
+ body[data-theme="auto"] .sphinx-tabs-tab[aria-selected="true"] {
+ border-bottom: 1px solid rgb(50, 50, 50);
+ background-color: rgb(50, 50, 50);
+ }
+}
+
+/* Explicit dark theme styling */
+
+body[data-theme="dark"] .sphinx-tabs-panel {
+ color: white;
+ background-color: rgb(50, 50, 50);
+}
+
+body[data-theme="dark"] .sphinx-tabs-tab {
+ color: white;
+ background-color: rgba(255, 255, 255, 0.05);
+}
+
+body[data-theme="dark"] .sphinx-tabs-tab[aria-selected="true"] {
+ border-bottom: 2px solid rgb(50, 50, 50);
+ background-color: rgb(50, 50, 50);
+}
diff --git a/branch/main/_static/tabs.js b/branch/main/_static/tabs.js
new file mode 100644
index 0000000..48dc303
--- /dev/null
+++ b/branch/main/_static/tabs.js
@@ -0,0 +1,145 @@
+try {
+ var session = window.sessionStorage || {};
+} catch (e) {
+ var session = {};
+}
+
+window.addEventListener("DOMContentLoaded", () => {
+ const allTabs = document.querySelectorAll('.sphinx-tabs-tab');
+ const tabLists = document.querySelectorAll('[role="tablist"]');
+
+ allTabs.forEach(tab => {
+ tab.addEventListener("click", changeTabs);
+ });
+
+ tabLists.forEach(tabList => {
+ tabList.addEventListener("keydown", keyTabs);
+ });
+
+ // Restore group tab selection from session
+ const lastSelected = session.getItem('sphinx-tabs-last-selected');
+ if (lastSelected != null) selectNamedTabs(lastSelected);
+});
+
+/**
+ * Key focus left and right between sibling elements using arrows
+ * @param {Node} e the element in focus when key was pressed
+ */
+function keyTabs(e) {
+ const tab = e.target;
+ let nextTab = null;
+ if (e.keyCode === 39 || e.keyCode === 37) {
+ tab.setAttribute("tabindex", -1);
+ // Move right
+ if (e.keyCode === 39) {
+ nextTab = tab.nextElementSibling;
+ if (nextTab === null) {
+ nextTab = tab.parentNode.firstElementChild;
+ }
+ // Move left
+ } else if (e.keyCode === 37) {
+ nextTab = tab.previousElementSibling;
+ if (nextTab === null) {
+ nextTab = tab.parentNode.lastElementChild;
+ }
+ }
+ }
+
+ if (nextTab !== null) {
+ nextTab.setAttribute("tabindex", 0);
+ nextTab.focus();
+ }
+}
+
+/**
+ * Select or deselect clicked tab. If a group tab
+ * is selected, also select tab in other tabLists.
+ * @param {Node} e the element that was clicked
+ */
+function changeTabs(e) {
+ // Use this instead of the element that was clicked, in case it's a child
+ const notSelected = this.getAttribute("aria-selected") === "false";
+ const positionBefore = this.parentNode.getBoundingClientRect().top;
+ const notClosable = !this.parentNode.classList.contains("closeable");
+
+ deselectTabList(this);
+
+ if (notSelected || notClosable) {
+ selectTab(this);
+ const name = this.getAttribute("name");
+ selectNamedTabs(name, this.id);
+
+ if (this.classList.contains("group-tab")) {
+ // Persist during session
+ session.setItem('sphinx-tabs-last-selected', name);
+ }
+ }
+
+ const positionAfter = this.parentNode.getBoundingClientRect().top;
+ const positionDelta = positionAfter - positionBefore;
+ // Scroll to offset content resizing
+ window.scrollTo(0, window.scrollY + positionDelta);
+}
+
+/**
+ * Select tab and show associated panel.
+ * @param {Node} tab tab to select
+ */
+function selectTab(tab) {
+ tab.setAttribute("aria-selected", true);
+
+ // Show the associated panel
+ document
+ .getElementById(tab.getAttribute("aria-controls"))
+ .removeAttribute("hidden");
+}
+
+/**
+ * Hide the panels associated with all tabs within the
+ * tablist containing this tab.
+ * @param {Node} tab a tab within the tablist to deselect
+ */
+function deselectTabList(tab) {
+ const parent = tab.parentNode;
+ const grandparent = parent.parentNode;
+
+ Array.from(parent.children)
+ .forEach(t => t.setAttribute("aria-selected", false));
+
+ Array.from(grandparent.children)
+ .slice(1) // Skip tablist
+ .forEach(panel => panel.setAttribute("hidden", true));
+}
+
+/**
+ * Select grouped tabs with the same name, but no the tab
+ * with the given id.
+ * @param {Node} name name of grouped tab to be selected
+ * @param {Node} clickedId id of clicked tab
+ */
+function selectNamedTabs(name, clickedId=null) {
+ const groupedTabs = document.querySelectorAll(`.sphinx-tabs-tab[name="${name}"]`);
+ const tabLists = Array.from(groupedTabs).map(tab => tab.parentNode);
+
+ tabLists
+ .forEach(tabList => {
+ // Don't want to change the tabList containing the clicked tab
+ const clickedTab = tabList.querySelector(`[id="${clickedId}"]`);
+ if (clickedTab === null ) {
+ // Select first tab with matching name
+ const tab = tabList.querySelector(`.sphinx-tabs-tab[name="${name}"]`);
+ deselectTabList(tab);
+ selectTab(tab);
+ }
+ })
+}
+
+if (typeof exports === 'undefined') {
+ exports = {};
+}
+
+exports.keyTabs = keyTabs;
+exports.changeTabs = changeTabs;
+exports.selectTab = selectTab;
+exports.deselectTabList = deselectTabList;
+exports.selectNamedTabs = selectNamedTabs;
diff --git a/branch/main/_static/term_role_formatting.css b/branch/main/_static/term_role_formatting.css
new file mode 100644
index 0000000..0b66095
--- /dev/null
+++ b/branch/main/_static/term_role_formatting.css
@@ -0,0 +1,4 @@
+/* Make terms bold */
+a.reference span.std-term {
+ font-weight: bold;
+}
diff --git a/branch/main/_static/togglebutton.css b/branch/main/_static/togglebutton.css
new file mode 100644
index 0000000..54a6787
--- /dev/null
+++ b/branch/main/_static/togglebutton.css
@@ -0,0 +1,160 @@
+/**
+ * Admonition-based toggles
+ */
+
+/* Visibility of the target */
+.admonition.toggle .admonition-title ~ * {
+ transition: opacity .3s, height .3s;
+}
+
+/* Toggle buttons inside admonitions so we see the title */
+.admonition.toggle {
+ position: relative;
+}
+
+/* Titles should cut off earlier to avoid overlapping w/ button */
+.admonition.toggle .admonition-title {
+ padding-right: 25%;
+ cursor: pointer;
+}
+
+/* Hovering will cause a slight shift in color to make it feel interactive */
+.admonition.toggle .admonition-title:hover {
+ box-shadow: inset 0 0 0px 20px rgb(0 0 0 / 1%);
+}
+
+/* Hovering will cause a slight shift in color to make it feel interactive */
+.admonition.toggle .admonition-title:active {
+ box-shadow: inset 0 0 0px 20px rgb(0 0 0 / 3%);
+}
+
+/* Remove extra whitespace below the admonition title when hidden */
+.admonition.toggle-hidden {
+ padding-bottom: 0;
+}
+
+.admonition.toggle-hidden .admonition-title {
+ margin-bottom: 0;
+}
+
+/* hides all the content of a page until de-toggled */
+.admonition.toggle-hidden .admonition-title ~ * {
+ height: 0;
+ margin: 0;
+ opacity: 0;
+ visibility: hidden;
+}
+
+/* General button style and position*/
+button.toggle-button {
+ /**
+ * Background and shape. By default there's no background
+ * but users can style as they wish
+ */
+ background: none;
+ border: none;
+ outline: none;
+
+ /* Positioning just inside the admonition title */
+ position: absolute;
+ right: 0.5em;
+ padding: 0px;
+ border: none;
+ outline: none;
+}
+
+/* Display the toggle hint on wide screens */
+@media (min-width: 768px) {
+ button.toggle-button.toggle-button-hidden:before {
+ content: attr(data-toggle-hint); /* This will be filled in by JS */
+ font-size: .8em;
+ align-self: center;
+ }
+}
+
+/* Icon behavior */
+.tb-icon {
+ transition: transform .2s ease-out;
+ height: 1.5em;
+ width: 1.5em;
+ stroke: currentColor; /* So that we inherit the color of other text */
+}
+
+/* The icon should point right when closed, down when open. */
+/* Open */
+.admonition.toggle button .tb-icon {
+ transform: rotate(90deg);
+}
+
+/* Closed */
+.admonition.toggle button.toggle-button-hidden .tb-icon {
+ transform: rotate(0deg);
+}
+
+/* With details toggles, we don't rotate the icon so it points right */
+details.toggle-details .tb-icon {
+ height: 1.4em;
+ width: 1.4em;
+ margin-top: 0.1em; /* To center the button vertically */
+}
+
+
+/**
+ * Details-based toggles.
+ * In this case, we wrap elements with `.toggle` in a details block.
+ */
+
+/* Details blocks */
+details.toggle-details {
+ margin: 1em 0;
+}
+
+
+details.toggle-details summary {
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ list-style: none;
+ border-radius: .2em;
+ border-left: 3px solid #1976d2;
+ background-color: rgb(204 204 204 / 10%);
+ padding: 0.2em 0.7em 0.3em 0.5em; /* Less padding on left because the SVG has left margin */
+ font-size: 0.9em;
+}
+
+details.toggle-details summary:hover {
+ background-color: rgb(204 204 204 / 20%);
+}
+
+details.toggle-details summary:active {
+ background: rgb(204 204 204 / 28%);
+}
+
+.toggle-details__summary-text {
+ margin-left: 0.2em;
+}
+
+details.toggle-details[open] summary {
+ margin-bottom: .5em;
+}
+
+details.toggle-details[open] summary .tb-icon {
+ transform: rotate(90deg);
+}
+
+details.toggle-details[open] summary ~ * {
+ animation: toggle-fade-in .3s ease-out;
+}
+
+@keyframes toggle-fade-in {
+ from {opacity: 0%;}
+ to {opacity: 100%;}
+}
+
+/* Print rules - we hide all toggle button elements at print */
+@media print {
+ /* Always hide the summary so the button doesn't show up */
+ details.toggle-details summary {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/branch/main/_static/togglebutton.js b/branch/main/_static/togglebutton.js
new file mode 100644
index 0000000..215a7ee
--- /dev/null
+++ b/branch/main/_static/togglebutton.js
@@ -0,0 +1,187 @@
+/**
+ * Add Toggle Buttons to elements
+ */
+
+let toggleChevron = `
+
+
+
+ `;
+
+var initToggleItems = () => {
+ var itemsToToggle = document.querySelectorAll(togglebuttonSelector);
+ console.log(`[togglebutton]: Adding toggle buttons to ${itemsToToggle.length} items`)
+ // Add the button to each admonition and hook up a callback to toggle visibility
+ itemsToToggle.forEach((item, index) => {
+ if (item.classList.contains("admonition")) {
+ // If it's an admonition block, then we'll add a button inside
+ // Generate unique IDs for this item
+ var toggleID = `toggle-${index}`;
+ var buttonID = `button-${toggleID}`;
+
+ item.setAttribute('id', toggleID);
+ if (!item.classList.contains("toggle")){
+ item.classList.add("toggle");
+ }
+ // This is the button that will be added to each item to trigger the toggle
+ var collapseButton = `
+
+ ${toggleChevron}
+ `;
+
+ title = item.querySelector(".admonition-title")
+ title.insertAdjacentHTML("beforeend", collapseButton);
+ thisButton = document.getElementById(buttonID);
+
+ // Add click handlers for the button + admonition title (if admonition)
+ admonitionTitle = document.querySelector(`#${toggleID} > .admonition-title`)
+ if (admonitionTitle) {
+ // If an admonition, then make the whole title block clickable
+ admonitionTitle.addEventListener('click', toggleClickHandler);
+ admonitionTitle.dataset.target = toggleID
+ admonitionTitle.dataset.button = buttonID
+ } else {
+ // If not an admonition then we'll listen for the button click
+ thisButton.addEventListener('click', toggleClickHandler);
+ }
+
+ // Now hide the item for this toggle button unless explicitly noted to show
+ if (!item.classList.contains("toggle-shown")) {
+ toggleHidden(thisButton);
+ }
+ } else {
+ // If not an admonition, wrap the block in a block
+ // Define the structure of the details block and insert it as a sibling
+ var detailsBlock = `
+
+
+ ${toggleChevron}
+ ${toggleHintShow}
+
+ `;
+ item.insertAdjacentHTML("beforebegin", detailsBlock);
+
+ // Now move the toggle-able content inside of the details block
+ details = item.previousElementSibling
+ details.appendChild(item)
+ item.classList.add("toggle-details__container")
+
+ // Set up a click trigger to change the text as needed
+ details.addEventListener('click', (click) => {
+ let parent = click.target.parentElement;
+ if (parent.tagName.toLowerCase() == "details") {
+ summary = parent.querySelector("summary");
+ details = parent;
+ } else {
+ summary = parent;
+ details = parent.parentElement;
+ }
+ // Update the inner text for the proper hint
+ if (details.open) {
+ summary.querySelector("span.toggle-details__summary-text").innerText = toggleHintShow;
+ } else {
+ summary.querySelector("span.toggle-details__summary-text").innerText = toggleHintHide;
+ }
+
+ });
+
+ // If we have a toggle-shown class, open details block should be open
+ if (item.classList.contains("toggle-shown")) {
+ details.click();
+ }
+ }
+ })
+};
+
+// This should simply add / remove the collapsed class and change the button text
+var toggleHidden = (button) => {
+ target = button.dataset['target']
+ var itemToToggle = document.getElementById(target);
+ if (itemToToggle.classList.contains("toggle-hidden")) {
+ itemToToggle.classList.remove("toggle-hidden");
+ button.classList.remove("toggle-button-hidden");
+ } else {
+ itemToToggle.classList.add("toggle-hidden");
+ button.classList.add("toggle-button-hidden");
+ }
+}
+
+var toggleClickHandler = (click) => {
+ // Be cause the admonition title is clickable and extends to the whole admonition
+ // We only look for a click event on this title to trigger the toggle.
+
+ if (click.target.classList.contains("admonition-title")) {
+ button = click.target.querySelector(".toggle-button");
+ } else if (click.target.classList.contains("tb-icon")) {
+ // We've clicked the icon and need to search up one parent for the button
+ button = click.target.parentElement;
+ } else if (click.target.tagName == "polyline") {
+ // We've clicked the SVG elements inside the button, need to up 2 layers
+ button = click.target.parentElement.parentElement;
+ } else if (click.target.classList.contains("toggle-button")) {
+ // We've clicked the button itself and so don't need to do anything
+ button = click.target;
+ } else {
+ console.log(`[togglebutton]: Couldn't find button for ${click.target}`)
+ }
+ target = document.getElementById(button.dataset['button']);
+ toggleHidden(target);
+}
+
+// If we want to blanket-add toggle classes to certain cells
+var addToggleToSelector = () => {
+ const selector = "";
+ if (selector.length > 0) {
+ document.querySelectorAll(selector).forEach((item) => {
+ item.classList.add("toggle");
+ })
+ }
+}
+
+// Helper function to run when the DOM is finished
+const sphinxToggleRunWhenDOMLoaded = cb => {
+ if (document.readyState != 'loading') {
+ cb()
+ } else if (document.addEventListener) {
+ document.addEventListener('DOMContentLoaded', cb)
+ } else {
+ document.attachEvent('onreadystatechange', function() {
+ if (document.readyState == 'complete') cb()
+ })
+ }
+}
+sphinxToggleRunWhenDOMLoaded(addToggleToSelector)
+sphinxToggleRunWhenDOMLoaded(initToggleItems)
+
+/** Toggle details blocks to be open when printing */
+if (toggleOpenOnPrint == "true") {
+ window.addEventListener("beforeprint", () => {
+ // Open the details
+ document.querySelectorAll("details.toggle-details").forEach((el) => {
+ el.dataset["togglestatus"] = el.open;
+ el.open = true;
+ });
+
+ // Open the admonitions
+ document.querySelectorAll(".admonition.toggle.toggle-hidden").forEach((el) => {
+ console.log(el);
+ el.querySelector("button.toggle-button").click();
+ el.dataset["toggle_after_print"] = "true";
+ });
+ });
+ window.addEventListener("afterprint", () => {
+ // Re-close the details that were closed
+ document.querySelectorAll("details.toggle-details").forEach((el) => {
+ el.open = el.dataset["togglestatus"] == "true";
+ delete el.dataset["togglestatus"];
+ });
+
+ // Re-close the admonition toggle buttons
+ document.querySelectorAll(".admonition.toggle").forEach((el) => {
+ if (el.dataset["toggle_after_print"] == "true") {
+ el.querySelector("button.toggle-button").click();
+ delete el.dataset["toggle_after_print"];
+ }
+ });
+ });
+}
diff --git a/branch/main/collaboration/code-review/index.html b/branch/main/collaboration/code-review/index.html
new file mode 100644
index 0000000..655b993
--- /dev/null
+++ b/branch/main/collaboration/code-review/index.html
@@ -0,0 +1,294 @@
+
+
+
+
+
+
+
+
+ Practicing code review — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Practicing code review
+In this episode we will practice the code review process. We will learn how to
+ask for changes in a pull request, how to suggest a change in a pull request,
+and how to modify a pull request.
+This will enable research groups to work more collaboratively and to not only
+improve the code quality but also to learn from each other .
+
+Exercise
+
+
Exercise preparation
+
We can continue in the same exercise repository which we have used in the
+previous episode.
+
+
+
Exercise: Practicing code review (25 min)
+
Technical requirements :
+
+
What is familiar from previous lessons:
+
+
What will be new in this exercise:
+
+As a reviewer, we will learn how to ask for changes in a pull request.
+As a reviewer, we will learn how to suggest a change in a pull request.
+As a submitter, we will learn how to modify a pull request without closing
+the incomplete one and opening a new one.
+
+
Exercise tasks :
+
+Create a new branch and one or few commits: in these improve something but also
+deliberately introduce a typo and also a larger mistake which we will want to fix during the code review.
+Open a pull request towards the main branch.
+As a reviewer to somebody else’s pull request, ask for an improvement and
+also directly suggest a change for the small typo. (Hint:
+suggestions are possible through the GitHub web interface, view of
+a pull request, “Files changed” view, after selecting some lines.
+Look for the “±” button.)
+As the submitter, learn how to accept the suggested change. (Hint:
+GitHub web interface, “Files Changed” view.)
+As the submitter, improve the pull request without having to close and open
+a new one: by adding a new commit to the same branch. (Hint: push
+to the branch again.)
+Once the changes are addressed, merge the pull request.
+
+
+
+
+Help and discussion
+From here on out, we don’t give detailed steps to the solution. You
+need to combine what you know, and the extra info below, in order to
+solve the above.
+
+How to ask for changes in a pull request
+Technically, there are at least two common ways to ask for changes in a pull
+request.
+Either in the comment field of the pull request:
+
+
+
+
+Or by using the “Review changes”:
+
+
+
+
+And always please be kind and constructive in your comments. Remember that the
+goal is not gate-keeping but collaborative learning .
+
+
+How to suggest a change in a pull request as a reviewer
+If you see a very small problem that is easy to fix, you can suggest a change
+as a reviewer.
+Instead of asking the submitter to tiny problem, you can suggest a change by
+clicking on the plus sign next to the line number in the “Files changed” tab:
+
+
+
+
+Here you can comment on specific lines or even line ranges.
+But now the interesting part is to click on the “Add a suggestion” symbol (the
+one that looks like plus and minus). Now you can fix the tiny problem (in this
+case a typo) and then click on the “Add single comment” button:
+
+
+
+
+The result is this and the submitter can accept the change with a single click:
+
+
+
+
+After accepting with “Commit suggestion”, the improvement gets added to the
+pull request.
+
+
+
+
+Summary
+
+Our process isn’t just about code now. It’s about discussion and
+working together to make the whole process better.
+GitHub (or GitLab) discussions and reviewing are quite powerful and can make
+small changes easy.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/collaboration/concepts/index.html b/branch/main/collaboration/concepts/index.html
new file mode 100644
index 0000000..176d324
--- /dev/null
+++ b/branch/main/collaboration/concepts/index.html
@@ -0,0 +1,235 @@
+
+
+
+
+
+
+
+
+ Concepts around collaboration — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Concepts around collaboration
+
+
+Commits, branches, repositories, forks, clones
+
+repository : The project, contains all data and history (commits, branches, tags).
+commit : Snapshot of the project, gets a unique identifier (e.g. c7f0e8bfc718be04525847fc7ac237f470add76e
).
+branch : Independent development line. The main development line is often called main
.
+tag : A pointer to one commit, to be able to refer to it later. Like a commemorative plaque
+that you attach to a particular commit (e.g. phd-printed
or paper-submitted
).
+cloning : Copying the whole repository to your laptop - the first time. It is not necessary to download each file one by one.
+forking : Taking a copy of a repository (which is typically not yours) - your
+copy (fork) stays on GitHub/GitLab and you can make changes to your copy.
+
+
+
+Cloning a repository
+In order to make a complete copy a whole repository, the git clone
command
+can be used. When cloning, all the files, of all or selected branches, of a
+repository are copied in one operation. Cloning of a repository is of relevance
+in a few different situations:
+
+Working on your own, cloning is the operation that you can use to create
+multiple instances of a repository on, for instance, a personal computer, a
+server, and a supercomputer.
+The parent repository could be a repository that you or your colleague own. A
+common use case for cloning is when working together within a smaller team
+where everyone has read and write access to the same git repository.
+Alternatively, cloning can be made from a public repository of a code that
+you would like to use. Perhaps you have no intention to work on the code, but
+would like to stay in tune with the latest developments, also in-between
+releases of new versions of the code.
+
+
+
+
+
+Forking and cloning
+
+
+
+
+Forking a repository
+When a fork is made on GitHub/GitLab a complete copy, of all or selected
+branches, of the repository is made. The copy will reside under a different
+account on GitHub/GitLab. Forking of a repository is of high relevance when
+working with a git repository to which you do not have write access.
+
+In the fork repository commits can be made to the base branch (main
or
+master
), and to other branches.
+The commits that are made within the branches of the fork repository can be
+contributed back to the parent repository by means of pull or merge requests.
+
+
+
+Synchronizing changes between repositories
+
+We need a mechanism to communicate changes between the repositories.
+We will pull or fetch updates from remote repositories (we will soon discuss the difference between pull and fetch).
+We will push updates to remote repositories.
+We will learn how to suggest changes within repositories on GitHub and across repositories (pull request ).
+Repositories that are forked or cloned do not automatically synchronize themselves:
+We will learn how to update forks (by pulling from the “central” repository).
+A main difference between cloning a repository and forking a repository is that the former is a general operation for generating copies of a repository to different computers, whereas forking is a particular operation implemented on GitHub/GitLab.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/collaboration/forking-workflow/index.html b/branch/main/collaboration/forking-workflow/index.html
new file mode 100644
index 0000000..fbe568d
--- /dev/null
+++ b/branch/main/collaboration/forking-workflow/index.html
@@ -0,0 +1,381 @@
+
+
+
+
+
+
+
+
+ How to contribute changes to repositories that belong to others — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+How to contribute changes to repositories that belong to others
+In this episode we prepare you to suggest and contribute changes to
+repositories that belong to others. These might be open source projects that
+you use in your work.
+We will see how Git and services like GitHub or GitLab can be used to suggest
+modification without having to ask for write access to the repository and
+accept modifications without having to grant write access to others.
+
+Exercise
+
+
Exercise preparation
+
+The exercise repository is now different:
+https://github.com/workshop-material/recipe-book-forking-exercise (note the -forking-exercise ).
+First fork the exercise repository to your GitHub account.
+Then clone your fork to your computer (if you wish to work locally).
+Double-check that you have forked the correct repository.
+
+
+
+
Exercise: Collaborating within the same repository (25 min)
+
Technical requirements :
+
+
What is familiar from previous lessons:
+
+
What will be new in this exercise:
+
+Opening a pull request towards the upstream repository.
+Pull requests can be coupled with automated testing.
+Learning that your fork can get out of date.
+After the pull requests are merged, updating your fork with the changes.
+Learn how to approach other people’s repositories with ideas, changes, and requests.
+
+
Exercise tasks :
+
+Open an issue in the upstream exercise repository where you describe the
+change you want to make. Take note of the issue number.
+Create a new branch in your fork of the repository.
+Make a change to the recipe book on the new branch and in the commit cross-reference the issue you opened.
+See the walk-through below for how to do this.
+Open a pull request towards the upstream repository.
+The instructor will review and merge the pull requests.
+During the review, pay attention to the automated test step (here for
+demonstration purposes, we test whether the recipe contains an ingredients
+and an instructions sections).
+After few pull requests are merged, update your fork with the changes.
+Check that in your fork you can see changes from other people’s pull requests.
+
+
+
+
+Help and discussion
+
+Help! I don’t have permissions to push my local changes
+Maybe you see an error like this one:
+ Please make sure you have the correct access rights
+and the repository exists.
+
+
+Or like this one:
+ failed to push some refs to workshop-material/recipe-book-forking-exercise.git
+
+
+In this case you probably try to push the changes not to your fork but to the original repository
+and in this exercise you do not have write access to the original repository.
+The simpler solution is to clone again but this time your fork.
+
+
Recovery
+
But if you want to keep your local changes, you can change the remote URL to point to your fork.
+Check where your remote points to with git remote --verbose
.
+
It should look like this (replace USER
with your GitHub username):
+
$ git remote --verbose
+
+origin git@github.com:USER/recipe-book-forking-exercise.git (fetch)
+origin git@github.com:USER/recipe-book-forking-exercise.git (push)
+
+
+
It should not look like this:
+
$ git remote --verbose
+
+origin git@github.com:workshop-material/recipe-book-forking-exercise.git (fetch)
+origin git@github.com:workshop-material/recipe-book-forking-exercise.git (push)
+
+
+
In this case you can adjust “origin” to point to your fork with:
+
$ git remote set-url origin git@github.com:USER/recipe-book-forking-exercise.git
+
+
+
+
+
+Opening a pull request towards the upstream repository
+We have learned in the previous episode that pull requests are always from
+branch to branch. But the branch can be in a different repository.
+When you open a pull request in a fork, by default GitHub will suggest to
+direct it towards the default branch of the upstream repository.
+This can be changed and it should always be verified, but in this case this is
+exactly what we want to do, from fork towards upstream:
+
+
+
+
+
+
+Pull requests can be coupled with automated testing
+We added an automated test here just for fun and so that you see that this is
+possible to do.
+In this exercise, the test is silly. It will check whether the recipe contains
+both an ingredients and an instructions section.
+In this example the test failed:
+
+
+
+
+Click on the “Details” link to see the details of the failed test:
+
+
+
+
+How can this be useful?
+
+The project can define what kind of tests are expected to pass before a pull
+request can be merged.
+The reviewer can see the results of the tests, without having to run them
+locally.
+
+How does it work?
+
+What tests or steps can you image for your project to run automatically with
+each pull request?
+
+
+How to update your fork with changes from upstream
+This used to be difficult but now it is two mouse clicks.
+Navigate to your fork and notice how GitHub tells you that your fork is behind.
+In my case, it is 9 commits behind upstream. To fix this, click on “Sync fork”
+and then “Update branch”:
+
+
+
+
+After the update my “branch is up to date” with the upstream repository:
+
+
+
+
+
+
+How to approach other people’s repositories with ideas, changes, and requests
+Contributing very minor changes
+
+If you observe an issue and have an idea how to fix it
+
+Open an issue in the repository you wish to contribute to
+Describe the problem
+If you have a suggestion on how to fix it, describe your suggestion
+Possibly discuss and get feedback
+If you are working on the fix, indicate it in the issue so that others know that somebody is working on it and who is working on it
+Submit your fix as pull request or merge request which references/closes the issue
+
+
+If you have an idea for a new feature
+
+Open an issue in the repository you wish to contribute to
+In the issue, write a short proposal for your suggested change or new feature
+Motivate why and how you wish to do this
+Also indicate where you are unsure and where you would like feedback
+Discuss and get feedback before you code
+Once you start coding, indicate that you are working on it
+Once you are done, submit your new feature as pull request or merge request which references/closes the issue/proposal
+
+
+
Motivation
+
+Get agreement and feedback before writing 5000 lines of code which might be rejected
+If we later wonder why something was done, we have the issue/proposal as
+reference and can read up on the reasoning behind a code change
+
+
+
+
+
+Summary
+
+This forking workflow lets you propose changes to repositories for
+which you have no write access .
+This is the way that much modern open-source software works.
+You can now contribute to any project you can view.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/collaboration/index.html b/branch/main/collaboration/index.html
new file mode 100644
index 0000000..a31c441
--- /dev/null
+++ b/branch/main/collaboration/index.html
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+ Collaborative version control and code review — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/collaboration/same-repository/index.html b/branch/main/collaboration/same-repository/index.html
new file mode 100644
index 0000000..4c395a0
--- /dev/null
+++ b/branch/main/collaboration/same-repository/index.html
@@ -0,0 +1,429 @@
+
+
+
+
+
+
+
+
+ Collaborating within the same repository — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Collaborating within the same repository
+In this episode, we will learn how to collaborate within the same repository.
+We will learn how to cross-reference issues and pull requests, how to review
+pull requests, and how to use draft pull requests.
+This exercise will form a good basis for collaboration that is suitable for
+most research groups.
+
+
Note
+
When you read or hear pull request , please think of a change proposal .
+
+
+Exercise
+In this exercise, we will contribute to a repository via a pull request .
+This means that you propose some change, and then it is accepted (or not).
+
+
Exercise preparation
+
+First we need to get access to the exercise repository to which we will
+contribute.
+
+
+Don’t forget to accept the invitation
+
+Check https://github.com/settings/organizations/
+Alternatively check the inbox for the email account you registered with
+GitHub. GitHub emails you an invitation link, but if you don’t receive it
+you can go to your GitHub notifications in the top right corner. The
+maintainer can also “copy invite link” and share it within the group.
+
+
+Watching and unwatching repositories
+
+Now that you are a collaborator, you get notified about new issues and pull
+requests via email.
+If you do not wish this, you can “unwatch” a repository (top of
+the project page).
+However, we recommend watching repositories you are interested
+in. You can learn things from experts just by watching the
+activity that come through a popular project.
+
+
+
+
+Unwatch a repository by clicking “Unwatch” in the repository view,
+then “Participating and @mentions” - this way, you will get
+notifications about your own interactions.
+
+
+
+
+
+
+
Exercise: Collaborating within the same repository (25 min)
+
Technical requirements (from installation instructions):
+
+
What is familiar from the previous workshop day (not repeated here):
+
+
What will be new in this exercise:
+
+If you create the changes locally, you will need to push them to the remote repository.
+Learning what a protected branch is and how to modify a protected branch: using a pull request.
+Cross-referencing issues and pull requests.
+Practice to review a pull request.
+Learn about the value of draft pull requests.
+
+
Exercise tasks :
+
+Start in the exercise
+repository and open an
+issue where you describe the change you want to make. Note down the issue
+number since you will need it later.
+Create a new branch.
+Make a change to the recipe book on the new branch and in the commit
+cross-reference the issue you opened (see the walk-through below
+for how to do that).
+Push your new branch (with the new commit) to the repository you
+are working on.
+Open a pull request towards the main branch.
+Review somebody else’s pull request and give constructive feedback. Merge their pull request.
+Try to create a new branch with some half-finished work and open a draft
+pull request. Verify that the draft pull request cannot be merged since it
+is not meant to be merged yet.
+
+
+
+
+Solution and hints
+
+(1) Opening an issue
+This is done through the GitHub web interface. For example, you could
+give the name of the recipe you want to add (so that others don’t add
+the same one). It is the “Issues” tab.
+
+
+(2) Create a new branch.
+If on GitHub, you can make the branch in the web interface.
+
+
+(3) Make a change adding the recipe
+Add a new file with the recipe in it. Commit the file. In the commit
+message, include the note about the issue number, saying that this
+will close that issue.
+
+Cross-referencing issues and pull requests
+Each issue and each pull request gets a number and you can cross-reference them.
+When you open an issue, note down the issue number (in this case it is #2
):
+
+
+
+
+You can reference this issue number in a commit message or in a pull request, like in this
+commit message:
+ this is the new recipe; fixes #2
+
+
+If you forget to do that in your commit message, you can also reference the issue
+in the pull request description. And instead of fixes
you can also use closes
or resolves
+or fix
or close
or resolve
(case insensitive).
+Here are all the keywords that GitHub recognizes:
+https://help.github.com/en/articles/closing-issues-using-keywords
+Then observe what happens in the issue once your commit gets merged: it will
+automatically close the issue and create a link between the issue and the
+commit. This is very useful for tracking what changes were made in response to
+which issue and to know from when until when precisely the issue was open.
+
+
+
+(4) Push to GitHub as a new branch
+Push the branch to the repository. You should end up with a branch
+visible in the GitHub web view.
+This is only necessary if you created the changes locally. If you created the
+changes directly on GitHub, you can skip this step.
+
+
VS Code Command line
In VS Code, you can “publish the branch” to the remote repository by clicking
+the cloud icon in the bottom left corner of the window:
+
+
+
+
+
If you have a local branch my-branch
and you want to push it to the remote
+and make it visible there, first verify what your remote is:
+
$ git remote --verbose
+
+origin git@github.com:USER/recipe-book.git (fetch)
+origin git@github.com:USER/recipe-book.git (push)
+
+
+
In this case the remote is called origin
and refers to the address
+git@github.com:USER/recipe-book.git. Both can be used
+interchangeably. Make sure it points to the right repository, ideally a
+repository that you can write to.
+
Now that you have a remote, you can push your branch to it:
+
$ git push origin my-branch
+
+
+
This will create a new branch on the remote repository with the same name as
+your local branch.
+
You can also do this:
+
$ git push --set-upstream origin my-branch
+
+
+
This will connect the local branch and the remote branch so that in the future
+you can just type git push
and git pull
without specifying the branch name
+(but this depends on your Git configuration).
+
Troubleshooting
+
If you don’t have a remote yet, you can add it with (adjust ADDRESS
to your repository address):
+
$ git remote add origin ADDRESS
+
+
+
ADDRESS is the part that you copy from here:
+
+
+
+
+
If the remote points to the wrong place, you can change it with:
+
$ git remote set-url origin NEWADDRESS
+
+
+
+
+
+(5) Open a pull request towards the main branch
+This is done through the GitHub web interface.
+
+
+(6) Reviewing pull requests
+You review through the GitHub web interface.
+Checklist for reviewing a pull request:
+
+Be kind, on the other side is a human who has put effort into this.
+Be constructive: if you see a problem, suggest a solution.
+Towards which branch is this directed?
+Is the title descriptive?
+Is the description informative?
+Scroll down to see commits.
+Scroll down to see the changes.
+If you get incredibly many changes, also consider the license or copyright
+and ask where all that code is coming from.
+Again, be kind and constructive.
+Later we will learn how to suggest changes directly in the pull request.
+
+If someone is new, it’s often nice to say something encouraging in the
+comments before merging (even if it’s just “thanks”). If all is good
+and there’s not much else to say, you could merge directly.
+
+
+(7) Draft pull requests
+Try to create a draft pull request:
+
+
+
+
+Verify that the draft pull request cannot be merged until it is marked as ready
+for review:
+
+
+
+
+Draft pull requests can be useful for:
+
+Feedback : You can open a pull request early to get feedback on your work without
+signaling that it is ready to merge.
+Information : They can help communicating to others that a change is coming up and in
+progress.
+
+
+
+What is a protected branch? And how to modify it?
+A protected branch on GitHub or GitLab is a branch that cannot (accidentally)
+deleted or force-pushed to. It is also possible to require that a branch cannot
+be directly pushed to or modified, but that changes must be submitted via a
+pull request.
+To protect a branch in your own repository, go to “Settings” -> “Branches”.
+
+
+Summary
+
+We used all the same pieces that we’ve learned previously.
+But we successfully contributed to a collaborative project !
+The pull request allowed us to contribute without changing directly:
+this is very good when it’s not mainly our project.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/credit/index.html b/branch/main/credit/index.html
new file mode 100644
index 0000000..9c83915
--- /dev/null
+++ b/branch/main/credit/index.html
@@ -0,0 +1,161 @@
+
+
+
+
+
+
+
+
+ Credit — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Credit
+This lesson is a mashup of the following sources (all CC-BY):
+
+The lesson uses the following example repository:
+
+The classification task has replaced the “planets” example repository used in
+the original lesson.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/dependencies/index.html b/branch/main/dependencies/index.html
new file mode 100644
index 0000000..5fb8309
--- /dev/null
+++ b/branch/main/dependencies/index.html
@@ -0,0 +1,617 @@
+
+
+
+
+
+
+
+
+ Reproducible environments and dependencies — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Reproducible environments and dependencies
+
+
Objectives
+
+There are not many codes that have no dependencies.
+How should we deal with dependencies ?
+We will focus on installing and managing dependencies in Python when using packages from PyPI and Conda.
+We will not discuss how to distribute your code as a package.
+
+
+[This episode borrows from https://coderefinery.github.io/reproducible-python/reusable/
+and https://aaltoscicomp.github.io/python-for-scicomp/dependencies/ ]
+Essential XKCD comics:
+
+
+How to avoid: “It works on my machine 🤷”
+Use a standard way to list dependencies in your project:
+
+Python: requirements.txt
or environment.yml
+R: DESCRIPTION
or renv.lock
+Rust: Cargo.lock
+Julia: Project.toml
+C/C++/Fortran: CMakeLists.txt
or Makefile
or spack.yaml
or the module
+system on clusters or containers
+Other languages: …
+
+
+
+Two ecosystems: PyPI (The Python Package Index) and Conda
+
+
PyPI
+
+Installation tool: pip
or uv
or similar
+Traditionally used for Python-only packages or
+for Python interfaces to external libraries. There are also packages
+that have bundled external libraries (such as numpy).
+Pros:
+
+Easy to use
+Package creation is easy
+
+
+Cons:
+
+
+
+
+
+
Conda
+
+Installation tool: conda
or mamba
or similar
+Aims to be a more general package distribution tool
+and it tries to provide not only the Python packages, but also libraries
+and tools needed by the Python packages.
+Pros:
+
+
+Cons:
+
+
+
+
+
+
+Conda ecosystem explained
+
+Anaconda is a distribution of conda packages
+made by Anaconda Inc. When using Anaconda remember to check that your
+situation abides with their licensing terms (see below).
+Anaconda has recently changed its licensing terms , which affects its
+use in a professional setting. This caused uproar among academia
+and Anaconda modified their position in
+this article .
+Main points of the article are:
+
+conda (installation tool) and community channels (e.g. conda-forge)
+are free to use.
+Anaconda repository and Anaconda’s channels in the community repository
+are free for universities and companies with fewer than 200 employees.
+Non-university research institutions and national laboratories need
+licenses.
+Miniconda is free, when it does not download Anaconda’s packages.
+Miniforge is not related to Anaconda, so it is free.
+
+For ease of use on sharing environment files, we recommend using
+Miniforge to create the environments and using conda-forge as the main
+channel that provides software.
+
+Major repositories/channels:
+
+Anaconda Repository
+houses Anaconda’s own proprietary software channels.
+Anaconda’s proprietary channels: main
, r
, msys2
and anaconda
.
+These are sometimes called defaults
.
+conda-forge is the largest open source
+community channel. It has over 28k packages that include open-source
+versions of packages in Anaconda’s channels.
+
+
+
+
+
+
+Best practice: Install dependencies into isolated environments
+
+For each project, create a separate environment .
+Don’t install dependencies globally for all projects. Sooner or later, different projects will have conflicting dependencies.
+Install them from a file which documents them at the same time
+Install dependencies by first recording them in requirements.txt
or
+environment.yml
and install using these files, then you have a trace
+(we will practice this later below).
+
+
+
Keypoints
+
If somebody asks you what dependencies you have in your project, you should be
+able to answer this question with a file .
+
In Python, the two most common ways to do this are:
+
+
You can export (“freeze”) the dependencies from your current environment into these files:
+
# inside a conda environment
+$ conda env export --from-history > environment.yml
+
+# inside a virtual environment
+$ pip freeze > requirements.txt
+
+
+
+
+
+How to communicate the dependencies as part of a report/thesis/publication
+Each notebook or script or project which depends on libraries should come with
+either a requirements.txt
or a environment.yml
, unless you are creating
+and distributing this project as Python package.
+
+Attach a requirements.txt
or a environment.yml
to your thesis.
+Even better: Put requirements.txt
or a environment.yml
in your Git repository along your code.
+Even better: Also binderize your analysis pipeline.
+
+
+
+Containers
+
+A container is like an operating system inside a file .
+“Building a container”: Container definition file (recipe) -> Container image
+This can be used with Apptainer /
+SingularityCE .
+
+Containers offer the following advantages:
+
+Reproducibility : The same software environment can be recreated on
+different computers. They force you to know and document all your dependencies .
+Portability : The same software environment can be run on different computers.
+Isolation : The software environment is isolated from the host system.
+“Time travel ”:
+
+
+
+
+
+How to install dependencies into environments
+Now we understand a bit better why and how we installed dependencies
+for this course in the Software install instructions .
+We have used Miniforge and the long command we have used was:
+$ mamba env create -n course -f https://raw.githubusercontent.com/coderefinery/reproducible-python-ml/main/software/environment.yml
+
+
+This command did two things:
+
+Create a new environment with name “course” (specified by -n
).
+Installed all dependencies listed in the environment.yml
file (specified by
+-f
), which we fetched directly from the web.
+Here
+you can browse it.
+
+For your own projects:
+
+Start by writing an environment.yml
of requirements.txt
file. They look like this:
+
+
+
environment.yml requirements.txt
name : course
+channels :
+ - conda-forge
+ - bioconda
+dependencies :
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - jupyterlab
+ - pytest
+ - scalene
+ - flit
+ - ruff
+ - icecream
+ - snakemake-minimal
+ - myst-parser
+ - sphinx
+ - sphinx-rtd-theme
+ - sphinx-autoapi
+ - sphinx-autobuild
+ - black
+ - isort
+ - pip
+ - pip :
+ - jupyterlab-code-formatter
+
+
+
click
+numpy
+pandas
+scipy
+altair
+vl - convert - python
+jupyterlab
+pytest
+scalene
+flit
+ruff
+icecream
+myst - parser
+sphinx
+sphinx - rtd - theme
+sphinx - autoapi
+sphinx - autobuild
+black
+isort
+jupyterlab - code - formatter
+
+
+
+
+Then set up an isolated environment and install the dependencies from the file into it:
+
+
+
Miniforge Pixi Virtual environment uv
+Create a new environment with name “myenv” from environment.yml
:
+$ conda env create -n myenv -f environment.yml
+
+
+Or equivalently:
+$ mamba env create -n myenv -f environment.yml
+
+
+
+Activate the environment:
+
+
+Run your code inside the activated virtual environment.
+
+
+
+
+Create a virtual environment by running (the second argument is the name
+of the environment and you can change it):
+
+
+Activate the virtual environment (how precisely depends on your operating
+system and shell).
+Install the dependencies:
+$ python -m pip install -r requirements.txt
+
+
+
+Run your code inside the activated virtual environment.
+
+
+
+
+Create a virtual environment by running (the second argument is the name
+of the environment and you can change it):
+
+
+Activate the virtual environment (how precisely depends on your operating
+system and shell).
+Install the dependencies:
+$ uv pip sync requirements.txt
+
+
+
+Run your code inside the virtual environment.
+$ uv run python example.py
+
+
+
+
+
+
+
+Updating environments
+What if you forgot a dependency? Or during the development of your project
+you realize that you need a new dependency? Or you don’t need some dependency anymore?
+
+Modify the environment.yml
or requirements.txt
file.
+Either remove your environment and create a new one, or update the existing one:
+
+
+
Miniforge Pixi Virtual environment uv
+
+
+Pinning package versions
+Let us look at the
+environment.yml
+which we used to set up the environment for this course.
+Dependencies are listed without version numbers. Should we pin the
+versions ?
+
+Both pip
and conda
ecosystems and all the tools that we have
+mentioned support pinning versions.
+It is possible to define a range of versions instead of precise versions.
+While your project is still in progress, I often use latest versions and do not pin them.
+When publishing the script or notebook, it is a good idea to pin the versions
+to ensure that the code can be run in the future.
+Remember that at some point in time you will face a situation where
+newer versions of the dependencies are no longer compatible with your
+software. At this point you’ll have to update your software to use the newer
+versions or to lock it into a place in time.
+
+
+
+Managing dependencies on a supercomputer
+
+Additional challenges:
+
+Storage quotas: Do not install dependencies in your home directory . A conda environment can easily contain 100k files.
+Network file systems struggle with many small files. Conda environments often contain many small files.
+
+
+Possible solutions:
+
+Try Pixi (modern take on managing Conda environments) and
+uv (modern take on managing virtual
+environments). Blog post: Using Pixi and uv on a supercomputer
+Install your environment on the fly into a scratch directory on local disk (not the network file system).
+Install your environment on the fly into a RAM disk/drive.
+Containerize your environment into a container image.
+
+
+
+
+
+
Keypoints
+
+Being able to communicate your dependencies is not only nice for others, but
+also for your future self or the next PhD student or post-doc.
+If you ask somebody to help you with your code, they will ask you for the
+dependencies.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/documentation/index.html b/branch/main/documentation/index.html
new file mode 100644
index 0000000..cb1f32c
--- /dev/null
+++ b/branch/main/documentation/index.html
@@ -0,0 +1,560 @@
+
+
+
+
+
+
+
+
+ Where to start with documentation — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Where to start with documentation
+
+
Objectives
+
+Discuss what makes good documentation.
+Improve the README of your project or our example project.
+Explore Sphinx which is a popular tool to build documentation websites.
+Learn how to leverage GitHub Actions and GitHub Pages to build and deploy documentation.
+
+
+
+
+Why? 💗✉️ to your future self
+
+
+
+In-code documentation
+Not very useful (more commentary than comment):
+# now we check if temperature is below -50
+if temperature < - 50 :
+ print ( "ERROR: temperature is too low" )
+
+
+More useful (explaining why ):
+# we regard temperatures below -50 degrees as measurement errors
+if temperature < - 50 :
+ print ( "ERROR: temperature is too low" )
+
+
+Keeping zombie code “just in case” (rather use version control):
+# do not run this code!
+# if temperature > 0:
+# print("It is warm")
+
+
+Emulating version control:
+# John Doe: threshold changed from 0 to 15 on August 5, 2013
+if temperature > 15 :
+ print ( "It is warm" )
+
+
+
+
+Many languages allow “docstrings”
+Example (Python):
+def kelvin_to_celsius ( temp_k : float ) -> float :
+ """
+ Converts temperature in Kelvin to Celsius.
+
+ Parameters
+ ----------
+ temp_k : float
+ temperature in Kelvin
+
+ Returns
+ -------
+ temp_c : float
+ temperature in Celsius
+ """
+ assert temp_k >= 0.0 , "ERROR: negative T_K"
+
+ temp_c = temp_k - 273.15
+
+ return temp_c
+
+
+
+
Keypoints
+
+Documentation which is only in the source code is not enough.
+Often a README is enough.
+Documentation needs to be kept
+in the same Git repository as the code since we want it to evolve with
+the code.
+
+
+
+
+Often a README is enough - checklist
+
+Purpose
+Requirements
+Installation instructions
+Copy-paste-able example to get started
+Tutorials covering key functionality
+Reference documentation (e.g. API) covering all functionality
+Authors and recommended citation
+License
+Contribution guide
+
+See also the
+JOSS review checklist .
+
+
+Diátaxis
+Diátaxis is a systematic approach to technical documentation authoring.
+
+
+
+What if you need more than a README?
+
+
+
+Exercise: Set up a Sphinx documentation
+
+
Preparation
+
In this episode we will use the following 5 packages which we installed
+previously as part of the Software install instructions :
+
myst - parser
+sphinx
+sphinx - rtd - theme
+sphinx - autoapi
+sphinx - autobuild
+
+
+
Which repository to use? You have 3 options:
+
+Clone your fork of the example repository.
+If you don’t have that, you can clone the exercise repository itself.
+You can try this with your own project and the project does not have to
+be a Python project.
+
+
+There are at least two ways to get started with Sphinx:
+
+Use sphinx-quickstart
to create a new Sphinx project.
+This is what we will do instead : Create three files (doc/conf.py
, doc/index.md
, and doc/about.md
)
+as starting point and improve from there.
+
+
+
Exercise: Set up a Sphinx documentation
+
+Create the following three files in your project:
+ your-project/
+├── doc/
+│ ├── conf.py
+│ ├── index.md
+│ └── about.md
+└── ...
+
+
+This is conf.py
:
+project = "your-project"
+copyright = "2025, Authors"
+author = "Authors"
+release = "0.1"
+
+exclude_patterns = [ "_build" , "Thumbs.db" , ".DS_Store" ]
+
+extensions = [
+ "myst_parser" , # in order to use markdown
+]
+
+myst_enable_extensions = [
+ "colon_fence" , # ::: can be used instead of ``` for better rendering
+]
+
+html_theme = "sphinx_rtd_theme"
+
+
+This is index.md
(feel free to change the example text):
+# Our code documentation
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
+incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
+nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
+culpa qui officia deserunt mollit anim id est laborum.
+
+:::{toctree}
+:maxdepth: 2
+:caption: Some caption
+
+about.md
+:::
+
+
+This is about.md
(feel free to adjust):
+# About this code
+
+Work in progress ...
+
+
+
+Run sphinx-build
to build the HTML documentation:
+$ sphinx-build doc _build
+
+... lots of output ...
+The HTML pages are in _build.
+
+
+
+Try to open _build/index.html
in your browser.
+Experiment with adding more content, images, equations, code blocks, …
+
+
+
+
+There is a lot more you can do:
+
+This is useful if you want to check the integrity of all internal and external links:
+$ sphinx-build doc -W -b linkcheck _build
+
+
+
+sphinx-autobuild
+provides a local web server that will automatically refresh your view
+every time you save a file - which makes writing with live-preview much easier.
+
+
+
+Demo: Building documentation with GitHub Actions
+
+First we need to extend the environment.yml
file to include the necessary packages:
+name : classification-task
+channels :
+ - conda-forge
+dependencies :
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - myst-parser
+ - sphinx
+ - sphinx-rtd-theme
+ - sphinx-autoapi
+
+
+Then we add a GitHub Actions workflow .github/workflow/sphinx.yml
to build the documentation:
+name : Build documentation
+
+on :
+ push :
+ branches : [ main ]
+ pull_request :
+ branches : [ main ]
+
+permissions :
+ contents : write
+
+jobs :
+ docs :
+ runs-on : ubuntu-24.04
+
+ steps :
+ - name : Checkout
+ uses : actions/checkout@v4
+
+ - uses : mamba-org/setup-micromamba@v1
+ with :
+ micromamba-version : '2.0.5-0' # any version from https://github.com/mamba-org/micromamba-releases
+ environment-file : environment.yml
+ init-shell : bash
+ cache-environment : true
+ post-cleanup : 'all'
+ generate-run-shell : false
+
+ - name : Sphinx build
+ run : |
+ sphinx-build doc _build
+ shell : bash -el {0}
+
+ - name : Deploy to GitHub Pages
+ uses : peaceiris/actions-gh-pages@v4
+ if : ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
+ with :
+ publish_branch : gh-pages
+ github_token : ${{ secrets.GITHUB_TOKEN }}
+ publish_dir : _build/
+ force_orphan : true
+
+
+Now:
+
+Add these two changes to the GitHub repository.
+Go to “Settings” -> “Pages” -> “Branch” -> gh-pages
-> “Save”.
+Look at “Actions” tab and observe the workflow running and hopefully
+deploying the website.
+Finally visit the generated site. You can find it by clicking the About wheel
+icon on top right of your repository. There, select “Use your GitHub Pages
+website”.
+This is how we build almost all of our lesson websites ,
+including this one!
+Another popular place to deploy Sphinx documentation is ReadTheDocs .
+
+
+
+Optional: How to auto-generate API documentation in Python
+Add three tiny modifications (highlighted) to doc/conf.py
to auto-generate API documentation
+(this requires the sphinx-autoapi
package):
+project = "your-project"
+copyright = "2025, Authors"
+author = "Authors"
+release = "0.1"
+
+exclude_patterns = [ "_build" , "Thumbs.db" , ".DS_Store" ]
+
+extensions = [
+ "myst_parser" , # in order to use markdown
+ "autoapi.extension" , # in order to use markdown
+ ]
+
+# search this directory for Python files
+autoapi_dirs = [ ".." ]
+
+# ignore this file when generating API documentation
+autoapi_ignore = [ "*/conf.py" ]
+
+myst_enable_extensions = [
+ "colon_fence" , # ::: can be used instead of ``` for better rendering
+]
+
+html_theme = "sphinx_rtd_theme"
+
+
+Then rebuild the documentation (or push the changes and let GitHub rebuild it)
+and you should see a new section “API Reference”.
+
+
+Possibilities to host Sphinx documentation
+
+
+
+Confused about reStructuredText vs. Markdown vs. MyST?
+
+At the beginning there was reStructuredText and Sphinx was built for reStructuredText.
+Independently, Markdown was invented and evolved into a couple of flavors.
+Markdown became more and more popular but was limited compared to reStructuredText.
+Later, MyST
+was invented to be able to write
+something that looks like Markdown but in addition can do everything that
+reStructuredText can do with extra directives.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/example/index.html b/branch/main/example/index.html
new file mode 100644
index 0000000..ada6834
--- /dev/null
+++ b/branch/main/example/index.html
@@ -0,0 +1,251 @@
+
+
+
+
+
+
+
+
+ Example project: 2D classification task using a nearest-neighbor predictor — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+ Example project: 2D classification task using a nearest-neighbor predictor
+
+ Edit on GitHub
+
+
+
+
+
+
+
+
+Example project: 2D classification task using a nearest-neighbor predictor
+The example code
+that we will study is a relatively simple nearest-neighbor predictor written in
+Python. It is not important or expected that we understand the code in detail.
+The code will produce something like this:
+
+
+
+
+The bottom row shows the training data (two labels) and the top row shows the
+test data and whether the nearest-neighbor predictor classified their labels
+correctly.
+
+
+The big picture of the code is as follows:
+
+We can choose the number of samples (the example above has 50 samples).
+The code will generate samples with two labels (0 and 1) in a 2D space.
+One of the labels has a normal distribution and a circular distribution with
+some minimum and maximum radius.
+The second label only has a circular distribution with a different radius.
+Then we try to predict whether the test samples belong to label 0 or 1 based
+on the nearest neighbors in the training data. The number of neighbors can
+be adjusted and the code will take label of the majority of the neighbors.
+
+
+Example run
+
+
Instructor note
+
The instructor demonstrates running the code on their computer.
+
+The code is written to accept command-line arguments to specify the number
+of samples and file names. Later we will discuss advantages of this approach.
+Let us try to get the help text:
+$ python generate_data.py --help
+
+Usage: generate_data.py [OPTIONS]
+
+ Program that generates a set of training and test samples for a non-linear
+ classification task.
+
+Options:
+ --num-samples INTEGER Number of samples for each class. [required]
+ --training-data TEXT Training data is written to this file. [required]
+ --test-data TEXT Test data is written to this file. [required]
+ --help Show this message and exit.
+
+
+We first generate the training and test data:
+$ python generate_data.py --num-samples 50 --training-data train.csv --test-data test.csv
+
+Generated 50 training samples (train.csv) and test samples (test.csv).
+
+
+In a second step we generate predictions for the test data:
+$ python generate_predictions.py --num-neighbors 7 --training-data train.csv --test-data test.csv --predictions predictions.csv
+
+Predictions saved to predictions.csv
+
+
+Finally, we can plot the results:
+$ python plot_results.py --training-data train.csv --predictions predictions.csv --output-chart chart.svg
+
+Accuracy: 0.94
+Saved chart to chart.svg
+
+
+
+
+Discussion and goals
+
+
+
+
We will not focus on …
+
+… how the code works internally in detail.
+… whether this is the most efficient algorithm.
+… whether the code is numerically stable.
+… how to code scales with system size.
+… whether it is portable to other operating systems (we will discuss this later).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/genindex/index.html b/branch/main/genindex/index.html
new file mode 100644
index 0000000..71173c8
--- /dev/null
+++ b/branch/main/genindex/index.html
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+ Index — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/good-practices/index.html b/branch/main/good-practices/index.html
new file mode 100644
index 0000000..b11cd5a
--- /dev/null
+++ b/branch/main/good-practices/index.html
@@ -0,0 +1,717 @@
+
+
+
+
+
+
+
+
+ Tools and useful practices — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/index.html b/branch/main/index.html
new file mode 100644
index 0000000..d8f3f08
--- /dev/null
+++ b/branch/main/index.html
@@ -0,0 +1,294 @@
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example) — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+ Edit on GitHub
+
+
+
+
+
+
+
+
+Reproducible research software development using Python (ML example)
+
+Big-picture goal
+This is a hands-on course on research software engineering . In this
+workshop we assume that most workshop participants use Python in their work or
+are leading a group which uses Python. Therefore, some of the examples will use
+Python as the example language.
+We will work with an example project (Example project: 2D classification task using a nearest-neighbor predictor )
+and go through all important steps of a typical
+software project. Once we have seen the building blocks, we will try to apply
+them to own projects .
+
+
Preparation
+
+Get a GitHub account following these instructions .
+You will need a text editor . If you don’t have a favorite one, we recommend
+VS Code .
+If you prefer to work in the terminal and not in VS Code, set up these two (skip this if you use VS Code):
+
+
+Follow Software install instructions (but we will do this together at the beginning of the workshop).
+
+
+
+
+Schedule
+
+
+
+Wednesday
+
+09:00-10:00 - Automated testing
+10:15-11:30 - Modular code development
+
+
+11:30-12:15 - Lunch break
+12:15-14:00 - How to release and publish your code
+
+
+14:15-15:00 - Debriefing and Q&A
+
+Participants work on their projects
+Together we study actual codes that participants wrote or work on
+Constructively we discuss possible improvements
+Give individual feedback on code projects
+
+
+
+
+
+Thursday
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/installation/index.html b/branch/main/installation/index.html
new file mode 100644
index 0000000..5b4bcaf
--- /dev/null
+++ b/branch/main/installation/index.html
@@ -0,0 +1,423 @@
+
+
+
+
+
+
+
+
+ Software install instructions — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Software install instructions
+[this page is adapted from https://aaltoscicomp.github.io/python-for-scicomp/installation/ ]
+
+Choosing an installation method
+For this course we will install an isolated environment
+with following dependencies:
+
+
environment.yml requirements.txt
name : course
+channels :
+ - conda-forge
+ - bioconda
+dependencies :
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - jupyterlab
+ - pytest
+ - scalene
+ - flit
+ - ruff
+ - icecream
+ - snakemake-minimal
+ - myst-parser
+ - sphinx
+ - sphinx-rtd-theme
+ - sphinx-autoapi
+ - sphinx-autobuild
+ - black
+ - isort
+ - pip
+ - pip :
+ - jupyterlab-code-formatter
+
+
+
click
+numpy
+pandas
+scipy
+altair
+vl - convert - python
+jupyterlab
+pytest
+scalene
+flit
+ruff
+icecream
+myst - parser
+sphinx
+sphinx - rtd - theme
+sphinx - autoapi
+sphinx - autobuild
+black
+isort
+jupyterlab - code - formatter
+
+
+
+
+If you are new to Python or unsure how to create isolated environments in
+Python from files above, please follow the
+instructions below.
+
+
There are many choices and we try to suggest a good compromise
+
There are very many ways to install Python and packages with pros and cons and
+in addition there are several operating systems with their own quirks. This
+can be a huge challenge for beginners to navigate. It can also difficult for
+instructors to give recommendations for something which will work everywhere
+and which everybody will like.
+
Below we will recommend Miniforge since it is free, open source, general,
+available on all operating systems, and provides a good basis for reproducible
+environments. However, it does not provide a graphical user interface during
+installation. This means that every time we want to start a JupyterLab session,
+we will have to go through the command line.
+
+
+
Python, conda, anaconda, miniforge, etc?
+
Unfortunately there are many options and a lot of jargon.
+Here is a crash course:
+
+Python is a programming language very commonly used in
+science, it’s the topic of this course.
+Conda is a package manager: it allows distributing and
+installing packages, and is designed for complex scientific
+code.
+Mamba is a re-implementation of Conda to be much faster with
+resolving dependencies and installing things.
+An environment is a self-contained collections of packages
+which can be installed separately from others. They are used so
+each project can install what it needs without affecting others.
+Anaconda is a commercial distribution of Python+Conda+many
+packages that all work together. It used to be freely usable for
+research, but since ~2023-2024 it’s more limited. Thus, we don’t
+recommend it (even though it has a nice graphical user interface).
+conda-forge is another channel of distributing packages that
+is maintained by the community, and thus can be used by anyone.
+(Anaconda’s parent company also hosts conda-forge packages)
+Miniforge is a distribution of conda pre-configured for
+conda-forge. It operates via the command line.
+Miniconda is a distribution of conda pre-configured to use
+the Anaconda channels.
+
+
We will gain a better background and overview in the section
+Reproducible environments and dependencies .
+
+
+
+Installing Python via Miniforge
+Follow the instructions on the miniforge web page . This installs
+the base, and from here other packages can be installed.
+Unsure what to download and what to do with it?
+
+
Windows MacOS Linux
You want to download and run Miniforge3-Windows-x86_64.exe
.
+
You probably want to download the Miniforge3-MacOSX-arm64.sh
file (unless
+you don’t have an Arm processor) and then run it in the terminal with:
+
$ bash Miniforge3-MacOSX-arm64.sh
+
+
+
You probably want to download the Miniforge3-Linux-x86_64.sh
file (unless
+you don’t have an x86_64 processor) and then run it in the terminal with:
+
$ bash Miniforge3-Linux-x86_64.sh
+
+
+
+
+
+Installing and activating the software environment
+First we will start Python in a way that activates conda/mamba. Then we will
+install the software environment from this environment.yml
+file .
+An environment is a self-contained set of extra libraries - different
+projects can use different environments to not interfere with each other. This
+environment will have all of the software needed for this particular course.
+We will call the environment course
.
+
+
Windows Linux / MacOS
Use the “Miniforge Prompt” to start Miniforge. This
+will set up everything so that conda
and mamba
are
+available.
+Then type
+(without the $
):
+
$ mamba env create -n course -f https://raw.githubusercontent.com/coderefinery/reproducible-python-ml/main/software/environment.yml
+
+
+
Each time you start a new command line terminal,
+you can activate Miniforge by running
+(without the $
):
+
$ source ~/miniforge3/bin/activate
+
+
+
This is needed so that
+Miniforge is usable wherever you need, but doesn’t affect any
+other software on your computer (this is not needed if you
+choose “Do you wish to update your shell profile to
+automatically initialize conda?”, but then it will always be
+active).
+
In the second step, we will install the software environment:
+
$ mamba env create -n course -f https://raw.githubusercontent.com/coderefinery/reproducible-python-ml/main/software/environment.yml
+
+
+
+
+
+Starting JupyterLab
+Every time we want to start a JupyterLab session,
+we will have to go through the command line and first
+activate the course
environment.
+
+
Windows Linux / MacOS
Start the Miniforge Prompt. Then type
+(without the $
):
+
$ conda activate course
+$ jupyter-lab
+
+
+
Start the terminal and in the terminal, type
+(without the $
):
+
$ source ~/miniforge3/bin/activate
+$ conda activate course
+$ jupyter-lab
+
+
+
+
+
+Removing the software environment
+
+
Windows Linux / MacOS
In the Miniforge Prompt, type
+(without the $
):
+
$ conda env list
+$ conda env remove --name course
+$ conda env list
+
+
+
In the terminal, type
+(without the $
):
+
$ source ~/miniforge3/bin/activate
+$ conda env list
+$ conda env remove --name course
+$ conda env list
+
+
+
+
+
+How to verify your installation
+Start JupyterLab (as described above). It will hopefully open up your browser
+and look like this:
+
+
+
+
+JupyterLab opened in the browser. Click on the Python 3 tile.
+
+
+Once you clicked the Python 3 tile it should look like this:
+
+
+
+
+Python 3 notebook started.
+
+
+Into that blue “cell” please type the following:
+import altair
+import pandas
+
+print ( "all good - ready for the course" )
+
+
+
+
+
+
+Please copy these lines and click on the “play”/”run” icon.
+
+
+This is how it should look:
+
+
+
+
+Screenshot after successful import.
+
+
+If this worked, you are all set and can close JupyterLab (no need to save these
+changes).
+This is how it should not look:
+
+
+
+
+Error: required packages could not be found.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/lesson.pdf b/branch/main/lesson.pdf
new file mode 100644
index 0000000..6ce3b3a
Binary files /dev/null and b/branch/main/lesson.pdf differ
diff --git a/branch/main/notebooks/sharing/index.html b/branch/main/notebooks/sharing/index.html
new file mode 100644
index 0000000..aac7188
--- /dev/null
+++ b/branch/main/notebooks/sharing/index.html
@@ -0,0 +1,246 @@
+
+
+
+
+
+
+
+
+ Sharing notebooks — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Sharing notebooks
+
+[this lesson is adapted after https://coderefinery.github.io/jupyter/sharing/ ]
+
+Document dependencies
+
+If you import libraries into your notebook, note down their versions.
+Document the dependencies as discussed in section Reproducible environments and dependencies .
+Place either environment.yml
or requirements.txt
in the same folder as
+the notebook(s).
+If you publish the notebook as part of a publication, it is probably a good
+idea to pin the versions of the libraries you use.
+This is not only useful for people who will try to rerun this in future, it
+is also understood by some tools (e.g. Binder ) which we
+will see later.
+
+
+
+Different ways to share a notebook
+We need to learn how to share notebooks. At the minimum we need
+to share them with our future selves (backup and reproducibility).
+
+The following platforms can be used free of charge but have paid subscriptions for
+faster access to cloud resources:
+
+
+
+Sharing dynamic notebooks using Binder
+
+
Exercise/demo: Making your notebooks reproducible by anyone (15 min)
+
Instructor demonstrates this:
+
+First we look at the statically rendered version of the example
+notebook
+on GitHub and also nbviewer .
+Visit https://mybinder.org :
+
+
+
+
+Check that your notebook repository now has a “launch binder”
+badge in your README.md
file on GitHub.
+Try clicking the button and see how your repository is launched
+on Binder (can take a minute or two). Your notebooks can now be
+explored and executed in the cloud.
+Enjoy being fully reproducible!
+
+
+
+
+How to get a digital object identifier (DOI)
+
+Zenodo is a great service to get a
+DOI for a notebook
+(but first practice with the Zenodo sandbox ).
+Binder can also run notebooks from Zenodo.
+In the supporting information of your paper you can refer to its DOI.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/notebooks/tooling/index.html b/branch/main/notebooks/tooling/index.html
new file mode 100644
index 0000000..a1f8044
--- /dev/null
+++ b/branch/main/notebooks/tooling/index.html
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+ Other useful tooling for notebooks — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/objects.inv b/branch/main/objects.inv
new file mode 100644
index 0000000..0ed9fab
Binary files /dev/null and b/branch/main/objects.inv differ
diff --git a/branch/main/packaging/index.html b/branch/main/packaging/index.html
new file mode 100644
index 0000000..fe538f1
--- /dev/null
+++ b/branch/main/packaging/index.html
@@ -0,0 +1,334 @@
+
+
+
+
+
+
+
+
+ Creating a Python package and deploying it to PyPI — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+ Creating a Python package and deploying it to PyPI
+
+ Edit on GitHub
+
+
+
+
+
+
+
+
+Creating a Python package and deploying it to PyPI
+
+
Objectives
+
In this episode we will create a pip-installable Python package and learn how
+to deploy it to PyPI. As example, we can use one of the Python scripts from our
+example repository.
+
+
+Creating a Python package with the help of Flit
+There are unfortunately many ways to package a Python project:
+
+setuptools
is the most common way to package a Python project. It is very
+powerful and flexible, but also can get complex.
+flit
is a simpler alternative to setuptools
. It is less versatile, but
+also easier to use.
+poetry
is a modern packaging tool which is more versatile than flit
and
+also easier to use than setuptools
.
+twine
is another tool to upload packages to PyPI.
+…
+
+
+
This will be a demo
+
+We will try to package the code together on the big screen.
+We will share the result on GitHub so that you can retrace the steps.
+In this demo, we will use Flit to package the code. From Why use Flit? :
+
+Make the easy things easy and the hard things possible is an old motto from
+the Perl community. Flit is entirely focused on the easy things part of that,
+and leaves the hard things up to other tools.
+
+
+
+
+
+
+
+Step 2: Testing an install from GitHub
+If a local install worked, push the pyproject.toml
to GitHub and try to install the package from GitHub.
+In a requirements.txt
file, you can specify the GitHub repository and the
+branch (adapt the names):
+git + https : // github . com / ORGANIZATION / REPOSITORY . git @main
+
+
+A corresponding envionment.yml
file for conda would look like this (adapt the
+names):
+name : experiment
+channels :
+ - conda-forge
+dependencies :
+ - python <= 3.12
+ - pip
+ - pip :
+ - git+https://github.com/ORGANIZATION/REPOSITORY.git@main
+
+
+Does it install and run? If yes, move on to the next step (test-PyPI and later
+PyPI).
+
+
+Step 3: Deploy the package to test-PyPI using GitHub Actions
+Our final step is to create a GitHub Actions workflow which will run when we
+create a new release.
+
+
Danger
+
I recommend to first practice this on the test-PyPI before deploying to the
+real PyPI.
+
+Here is the workflow (.github/workflows/package.yml
):
+name : Package
+
+on :
+ release :
+ types : [ created ]
+
+jobs :
+ build :
+ permissions : write-all
+ runs-on : ubuntu-24.04
+
+ steps :
+ - name : Switch branch
+ uses : actions/checkout@v4
+ - name : Set up Python
+ uses : actions/setup-python@v5
+ with :
+ python-version : "3.12"
+ - name : Install Flit
+ run : |
+ pip install flit
+ - name : Flit publish
+ run :
+ flit publish
+ env :
+ FLIT_USERNAME : __token__
+ FLIT_PASSWORD : ${{ secrets.PYPI_TOKEN }}
+ # uncomment the following line if you are using test.pypi.org:
+# FLIT_INDEX_URL: https://test.pypi.org/legacy/
+ # this is how you can test the installation from test.pypi.org:
+# pip install --index-url https://test.pypi.org/simple/ package_name
+
+
+About the FLIT_PASSWORD: ${{ secrets.PYPI_TOKEN }}
:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/profiling/index.html b/branch/main/profiling/index.html
new file mode 100644
index 0000000..63cafec
--- /dev/null
+++ b/branch/main/profiling/index.html
@@ -0,0 +1,432 @@
+
+
+
+
+
+
+
+
+ Profiling — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Profiling
+
+
Objectives
+
+Understand when improving code performance is worth the time and effort.
+Knowing how to find performance bottlenecks in Python code.
+Try Scalene as one of many tools
+to profile Python code.
+
+
+[This page is adapted after https://aaltoscicomp.github.io/python-for-scicomp/profiling/ ]
+
+Should we even optimize the code?
+Classic quote to keep in mind: “Premature optimization is the root of all evil.” [Donald Knuth]
+
+
Discussion
+
It is important to ask ourselves whether it is worth it.
+
+
Depends. What does it depend on?
+
+
+
+Measure instead of guessing
+Before doing code surgery to optimize the run time or lower the memory usage,
+we should measure where the bottlenecks are. This is called profiling .
+Analogy: Medical doctors don’t start surgery based on guessing. They first measure
+(X-ray, MRI, …) to know precisely where the problem is.
+Not only programming beginners can otherwise guess wrong, but also experienced
+programmers can be surprised by the results of profiling.
+
+
+
+
+Tracing profilers vs. sampling profilers
+Tracing profilers record every function call and event in the program,
+logging the exact sequence and duration of events.
+
+Sampling profilers periodically samples the program’s state (where it is
+and how much memory is used), providing a statistical view of where time is
+spent.
+
+Pros:
+
+
+Cons:
+
+Less precise, potentially missing infrequent or short calls.
+Provides an approximation rather than exact timing.
+
+
+
+
+
Analogy: Imagine we want to optimize the London Underground (subway) system
+
We wish to detect bottlenecks in the system to improve the service and for this we have
+asked few passengers to help us by tracking their journey.
+
+Tracing : We follow every train and passenger, recording every stop
+and delay. When passengers enter and exit the train, we record the exact time
+and location.
+Sampling : Every 5 minutes the phone notifies the passenger to note
+down their current location. We then use this information to estimate
+the most crowded stations and trains.
+
+
+
+
+Choosing the right system size
+Sometimes we can configure the system size (for instance the time step in a simulation
+or the number of time steps or the matrix dimensions) to make the program finish sooner.
+For profiling, we should choose a system size that is representative of the real-world
+use case. If we profile a program with a small input size, we might not see the same
+bottlenecks as when running the program with a larger input size.
+Often, when we scale up the system size, or scale the number of processors, new bottlenecks
+might appear which we didn’t see before. This brings us back to: “measure instead of guessing”.
+
+
+Exercises
+
+
Exercise: Practicing profiling
+
In this exercise we will use the Scalene profiler to find out where most of the time is spent
+and most of the memory is used in a given code example.
+
Please try to go through the exercise in the following steps:
+
+Make sure scalene
is installed in your environment (if you have followed
+this course from the start and installed the recommended software
+environment, then it is).
+Download Leo Tolstoy’s “War and Peace” from the following link (the text is
+provided by Project Gutenberg ):
+https://www.gutenberg.org/cache/epub/2600/pg2600.txt
+(right-click and “save as” to download the file and save it as “book.txt” ).
+Before you run the profiler, try to predict in which function the code
+(the example code is below)
+will spend most of the time and in which function it will use most of the
+memory.
+Save the example code as example.py
and
+run the scalene
profiler on the following code example and browse the
+generated HTML report to find out where most of the time is spent and where
+most of the memory is used:
+
+Alternatively you can do this (and then open the generated file in a browser):
+$ scalene example.py --html > profile.html
+
+
+You can find an example of the generated HTML report in the solution below.
+
+Does the result match your prediction? Can you explain the results?
+
+
Example code (example.py
):
+
"""
+The code below reads a text file and counts the number of unique words in it
+(case-insensitive).
+"""
+import re
+
+
+def count_unique_words1 ( file_path : str ) -> int :
+ with open ( file_path , "r" , encoding = "utf-8" ) as file :
+ text = file . read ()
+ words = re . findall ( r "\b\w+\b" , text . lower ())
+ return len ( set ( words ))
+
+
+def count_unique_words2 ( file_path : str ) -> int :
+ unique_words = []
+ with open ( file_path , "r" , encoding = "utf-8" ) as file :
+ for line in file :
+ words = re . findall ( r "\b\w+\b" , line . lower ())
+ for word in words :
+ if word not in unique_words :
+ unique_words . append ( word )
+ return len ( unique_words )
+
+
+def count_unique_words3 ( file_path : str ) -> int :
+ unique_words = set ()
+ with open ( file_path , "r" , encoding = "utf-8" ) as file :
+ for line in file :
+ words = re . findall ( r "\b\w+\b" , line . lower ())
+ for word in words :
+ unique_words . add ( word )
+ return len ( unique_words )
+
+
+def main ():
+ # book.txt is downloaded from https://www.gutenberg.org/cache/epub/2600/pg2600.txt
+ _result = count_unique_words1 ( "book.txt" )
+ _result = count_unique_words2 ( "book.txt" )
+ _result = count_unique_words3 ( "book.txt" )
+
+
+if __name__ == "__main__" :
+ main ()
+
+
+
+
Solution
+
+
+
+
+Result of the profiling run for the above code example. You can click on the image to make it larger.
+
+
+
Results:
+
+
Explanation:
+
+The count_unique_words2
function is the slowest because it uses a list
+to store unique words and checks if a word is already in the list before
+adding it.
+Checking whether a list contains an element might require traversing the
+whole list, which is an O(n) operation. As the list grows in size,
+the lookup time increases with the size of the list.
+The count_unique_words1
and count_unique_words3
functions are faster
+because they use a set to store unique words.
+Checking whether a set contains an element is an O(1) operation.
+The count_unique_words1
function uses the most memory because it creates
+a list of all words in the text file and then creates a set from that
+list.
+The count_unique_words3
function uses less memory because it traverses
+the text file line by line instead of reading the whole file into memory.
+
+
What we can learn from this exercise:
+
+When processing large files, it can be good to read them line by line
+or in batches
+instead of reading the whole file into memory.
+It is good to get an overview over standard data structures and their
+advantages and disadvantages (e.g. adding an element to a list is fast but checking whether
+it already contains the element can be slow).
+
+
+
+
+
+Additional resources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/publishing/index.html b/branch/main/publishing/index.html
new file mode 100644
index 0000000..59c0acd
--- /dev/null
+++ b/branch/main/publishing/index.html
@@ -0,0 +1,305 @@
+
+
+
+
+
+
+
+
+ How to publish your code — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+How to publish your code
+
+
+Is putting software on GitHub/GitLab/… publishing?
+
+
+
+
+FAIR principles. (c) Scriberia for The Turing Way , CC-BY.
+
+
+Is it enough to make the code public for the code to remain findable and accessible ?
+
+No. Because nothing prevents me from deleting my GitHub repository or
+rewriting the Git history and we have no guarantee that GitHub will still be around in 10 years.
+Make your code citable and persistent :
+Get a persistent identifier (PID) such as DOI in addition to sharing the
+code publicly, by using services like Zenodo or
+similar services.
+
+
+
+How to make your software citable
+
+
Discussion: Explain how you currently cite software
+
+Do you cite software that you use? How?
+If I wanted to cite your code/scripts, what would I need to do?
+
+
+Checklist for making a release of your software citable :
+
+Assigned an appropriate license
+Described the software using an appropriate metadata format
+Clear version number
+Authors credited
+Procured a persistent identifier
+Added a recommended citation to the software documentation
+
+This checklist is adapted from: N. P. Chue Hong, A. Allen, A. Gonzalez-Beltran,
+et al., Software Citation Checklist for Developers (Version 0.9.0). Zenodo.
+2019b. (DOI )
+Our practical recommendations :
+
+This is an example of a simple CITATION.cff
file:
+cff-version : 1.2.0
+message : "If you use this software, please cite it as below."
+authors :
+ - family-names : Doe
+ given-names : Jane
+ orcid : https://orcid.org/1234-5678-9101-1121
+title : "My Research Software"
+version : 2.0.4
+doi : 10.5281/zenodo.1234
+date-released : 2021-08-11
+
+
+More about CITATION.cff
files:
+
+
+
+
+How to cite software
+
+
Great resources
+
+A. M. Smith, D. S. Katz, K. E. Niemeyer, and FORCE11 Software Citation
+Working Group, “Software citation principles,” PeerJ Comput. Sci., vol. 2,
+no. e86, 2016 (DOI )
+D. S. Katz, N. P. Chue Hong, T. Clark, et al., Recognizing the value of
+software: a software citation guide [version 2; peer review: 2 approved].
+F1000Research 2021, 9:1257 (DOI )
+N. P. Chue Hong, A. Allen, A. Gonzalez-Beltran, et al., Software Citation
+Checklist for Authors (Version 0.9.0). Zenodo. 2019a. (DOI )
+N. P. Chue Hong, A. Allen, A. Gonzalez-Beltran, et al., Software Citation
+Checklist for Developers (Version 0.9.0). Zenodo. 2019b. (DOI )
+
+
+Recommended format for software citation is to ensure the following information
+is provided as part of the reference (from Katz, Chue Hong, Clark,
+2021 which also contains
+software citation examples):
+
+Creator
+Title
+Publication venue
+Date
+Identifier
+Version
+Type
+
+
+
+Exercise/demo
+
+
Exercise
+
+We will add a CITATION.cff
file to our example repository.
+We will get a DOI using the Zenodo sandbox :
+
+
+We can try to create an example repository with a Jupyter Notebook and run it through Binder
+to make it persistent and citable.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/refactoring-concepts/index.html b/branch/main/refactoring-concepts/index.html
new file mode 100644
index 0000000..9380b5a
--- /dev/null
+++ b/branch/main/refactoring-concepts/index.html
@@ -0,0 +1,344 @@
+
+
+
+
+
+
+
+
+ Concepts in refactoring and modular code design — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+ Concepts in refactoring and modular code design
+
+ Edit on GitHub
+
+
+
+
+
+
+
+
+Concepts in refactoring and modular code design
+
+Starting questions for the collaborative document
+
+What does “modular code development” mean for you?
+What best practices can you recommend to arrive at well structured,
+modular code in your favourite programming language?
+What do you know now about programming that you wish somebody told you
+earlier?
+Do you design a new code project on paper before coding? Discuss pros and
+cons.
+Do you build your code top-down (starting from the big picture) or
+bottom-up (starting from components)? Discuss pros and cons.
+Would you prefer your code to be 2x slower if it was easier to read and
+understand?
+
+
+
+Pure functions
+
+Pure functions have no notion of state: They take input values and return
+values
+Given the same input, a pure function always returns the same value
+Function calls can be optimized away
+Pure function == data
+
+a) pure: no side effects
+def fahrenheit_to_celsius ( temp_f ):
+ temp_c = ( temp_f - 32.0 ) * ( 5.0 / 9.0 )
+ return temp_c
+
+temp_c = fahrenheit_to_celsius ( temp_f = 100.0 )
+print ( temp_c )
+
+
+b) stateful: side effects
+f_to_c_offset = 32.0
+f_to_c_factor = 0.555555555
+temp_c = 0.0
+
+def fahrenheit_to_celsius_bad ( temp_f ):
+ global temp_c
+ temp_c = ( temp_f - f_to_c_offset ) * f_to_c_factor
+
+fahrenheit_to_celsius_bad ( temp_f = 100.0 )
+print ( temp_c )
+
+
+Pure functions are easier to:
+
+Test
+Understand
+Reuse
+Parallelize
+Simplify
+Optimize
+Compose
+
+Mathematical functions are pure:
+
+\[f(x, y) = x - x^2 + x^3 + y^2 + xy\]
+
+\[(f \circ g)(x) = f(g(x))\]
+Unix shell commands are stateless:
+ $ cat somefile | grep somestring | sort | uniq | ...
+
+
+
+
+But I/O and network and disk and databases are not pure!
+
+
+
+
+
+
+From classes to functions
+Object-oriented programming and functional programming both have their place
+and value .
+Here is an example of expressing the same thing in Python in 4 different ways.
+Which one do you prefer?
+
+As a class :
+import math
+
+
+class Moon :
+ def __init__ ( self , name , radius , contains_water = False ):
+ self . name = name
+ self . radius = radius # in kilometers
+ self . contains_water = contains_water
+
+ def surface_area ( self ) -> float :
+ """Calculate the surface area of the moon assuming a spherical shape."""
+ return 4.0 * math . pi * self . radius ** 2
+
+ def __repr__ ( self ):
+ return f "Moon(name= { self . name !r} , radius= { self . radius } , contains_water= { self . contains_water } )"
+
+
+europa = Moon ( name = "Europa" , radius = 1560.8 , contains_water = True )
+
+print ( europa )
+print ( f "Surface area (km^2) of { europa . name } : { europa . surface_area () } " )
+
+
+
+As a dataclass :
+from dataclasses import dataclass
+import math
+
+
+@dataclass
+class Moon :
+ name : str
+ radius : float # in kilometers
+ contains_water : bool = False
+
+ def surface_area ( self ) -> float :
+ """Calculate the surface area of the moon assuming a spherical shape."""
+ return 4.0 * math . pi * self . radius ** 2
+
+
+europa = Moon ( name = "Europa" , radius = 1560.8 , contains_water = True )
+
+print ( europa )
+print ( f "Surface area (km^2) of { europa . name } : { europa . surface_area () } " )
+
+
+
+As a named tuple :
+import math
+from collections import namedtuple
+
+
+def surface_area ( radius : float ) -> float :
+ return 4.0 * math . pi * radius ** 2
+
+
+Moon = namedtuple ( "Moon" , [ "name" , "radius" , "contains_water" ])
+
+
+europa = Moon ( name = "Europa" , radius = 1560.8 , contains_water = True )
+
+print ( europa )
+print ( f "Surface area (km^2) of { europa . name } : { surface_area ( europa . radius ) } " )
+
+
+
+As a dict :
+import math
+
+
+def surface_area ( radius : float ) -> float :
+ return 4.0 * math . pi * radius ** 2
+
+
+europa = { "name" : "Europa" , "radius" : 1560.8 , "contains_water" : True }
+
+
+print ( europa )
+print ( f "Surface area (km^2) of { europa [ 'name' ] } : { surface_area ( europa [ 'radius' ]) } " )
+
+
+
+
+
+
+How to design your code before writing it
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/search/index.html b/branch/main/search/index.html
new file mode 100644
index 0000000..d0e7c40
--- /dev/null
+++ b/branch/main/search/index.html
@@ -0,0 +1,163 @@
+
+
+
+
+
+
+
+ Search — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/searchindex.js b/branch/main/searchindex.js
new file mode 100644
index 0000000..1e85ff5
--- /dev/null
+++ b/branch/main/searchindex.js
@@ -0,0 +1 @@
+Search.setIndex({"alltitles": {"(1) Basic browsing": [[22, "basic-browsing"]], "(1) Create a new branch and a new commit": [[21, "create-a-new-branch-and-a-new-commit"]], "(1) Navigate to your branch": [[24, "navigate-to-your-branch"]], "(1) Opening an issue": [[4, "opening-an-issue"]], "(2) Begin the pull request process": [[24, "begin-the-pull-request-process"]], "(2) Compare commit history with network graph": [[22, "compare-commit-history-with-network-graph"]], "(2) Create a new branch.": [[4, "create-a-new-branch"]], "(2) Modify the file again with a new commit": [[21, "modify-the-file-again-with-a-new-commit"]], "(3) Fill out and verify the pull request": [[24, "fill-out-and-verify-the-pull-request"]], "(3) How can you browse the history of a single file?": [[22, "how-can-you-browse-the-history-of-a-single-file"]], "(3) Make a change adding the recipe": [[4, "make-a-change-adding-the-recipe"]], "(3) Switch to the main branch and create a commit there": [[21, "switch-to-the-main-branch-and-create-a-commit-there"]], "(4) Browse the commits you just made": [[21, "browse-the-commits-you-just-made"]], "(4) Create the pull request": [[24, "create-the-pull-request"]], "(4) Push to GitHub as a new branch": [[4, "push-to-github-as-a-new-branch"]], "(4) Which files include the word \u201ctraining\u201d?": [[22, "which-files-include-the-word-training"]], "(5) Compare the branches": [[21, "compare-the-branches"]], "(5) Merge the pull request": [[24, "merge-the-pull-request"]], "(5) Open a pull request towards the main branch": [[4, "open-a-pull-request-towards-the-main-branch"]], "(5) Who modified a particular line last and when?": [[22, "who-modified-a-particular-line-last-and-when"]], "(6) Can you use this code yourself? Are you allowed to share modifications?": [[22, "can-you-use-this-code-yourself-are-you-allowed-to-share-modifications"]], "(6) Compare two arbitrary commits": [[21, "compare-two-arbitrary-commits"]], "(6) Delete merged branches": [[24, "delete-merged-branches"]], "(6) Reviewing pull requests": [[4, "reviewing-pull-requests"]], "(7) Contribute to the original repository with a pull request": [[24, "contribute-to-the-original-repository-with-a-pull-request"]], "(7) Draft pull requests": [[4, "draft-pull-requests"]], "(7) Renaming a branch": [[21, "renaming-a-branch"]], "(8) Creating a tag": [[21, "creating-a-tag"]], "AI tools open up a box of questions which are beyond our scope here": [[9, null]], "Adding the unit test to GitHub Actions": [[19, "adding-the-unit-test-to-github-actions"]], "Additional resources": [[15, "additional-resources"]], "Analogy: Imagine we want to optimize the London Underground (subway) system": [[15, "discussion-1"]], "Automated testing": [[19, null]], "Avoiding conflicts": [[23, "avoiding-conflicts"]], "Background": [[21, "background"]], "Best practice: Install dependencies into isolated environments": [[6, "best-practice-install-dependencies-into-isolated-environments"]], "Big-picture goal": [[10, "big-picture-goal"]], "But I/O and network and disk and databases are not pure!": [[17, "but-i-o-and-network-and-disk-and-databases-are-not-pure"]], "Choosing a software license": [[18, null]], "Choosing an installation method": [[11, "choosing-an-installation-method"]], "Choosing the right system size": [[15, "choosing-the-right-system-size"]], "Cloning a repository": [[2, "cloning-a-repository"]], "Code formatting": [[13, "code-formatting"]], "Collaborating within the same repository": [[4, null]], "Collaborative version control and code review": [[0, null]], "Commits, branches, repositories, forks, clones": [[2, "commits-branches-repositories-forks-clones"]], "Concepts around collaboration": [[2, null]], "Concepts in refactoring and modular code design": [[17, null]], "Conda": [[6, null]], "Conda ecosystem explained": [[6, "conda-ecosystem-explained"]], "Conflict resolution": [[23, null]], "Confused about reStructuredText vs. Markdown vs. MyST?": [[7, "confused-about-restructuredtext-vs-markdown-vs-myst"]], "Consider annotating your functions with type hints": [[9, "consider-annotating-your-functions-with-type-hints"]], "Consider using AI-assisted coding": [[9, "consider-using-ai-assisted-coding"]], "Containers": [[6, "containers"]], "Copyright and derivative work: Sampling/remixing": [[18, "copyright-and-derivative-work-sampling-remixing"]], "Creating a Python package and deploying it to PyPI": [[14, null]], "Creating a Python package with the help of Flit": [[14, "creating-a-python-package-with-the-help-of-flit"]], "Creating a copy of the repository by \u201cforking\u201d or \u201ccloning\u201d": [[22, "creating-a-copy-of-the-repository-by-forking-or-cloning"]], "Creating branches and commits": [[21, null]], "Credit": [[5, null]], "Cross-referencing issues and pull requests": [[4, "cross-referencing-issues-and-pull-requests"]], "Debugging with print statements": [[9, "debugging-with-print-statements"]], "Demo: Building documentation with GitHub Actions": [[7, "demo-building-documentation-with-github-actions"]], "Demonstration": [[25, "demonstration"]], "Different ways to share a notebook": [[12, "different-ways-to-share-a-notebook"]], "Discussion": [[8, "discussion-0"], [15, "discussion-0"], [16, "discussion-1"], [18, "discussion-0"], [25, "discussion-0"]], "Discussion and goals": [[8, "discussion-and-goals"]], "Discussion: Explain how you currently cite software": [[16, "discussion-0"]], "Di\u00e1taxis": [[7, "diataxis"]], "Document dependencies": [[12, "document-dependencies"]], "End-to-end tests": [[19, "end-to-end-tests"]], "Enumerate if you need the index": [[9, "enumerate-if-you-need-the-index"]], "Example project: 2D classification task using a nearest-neighbor predictor": [[8, null]], "Example run": [[8, "example-run"]], "Exercise": [[1, "exercise"], [3, "exercise"], [4, "exercise"], [16, "exercise-0"], [18, "exercise-0"], [19, "exercise-0"], [24, "exercise"], [27, "exercise"]], "Exercise preparation": [[1, "prerequisites-0"], [3, "prerequisites-0"], [4, "prerequisites-0"], [22, "prerequisites-0"]], "Exercise/demo": [[16, "exercise-demo"], [18, "exercise-demo"]], "Exercise/demo: Making your notebooks reproducible by anyone (15 min)": [[12, "exercise-0"]], "Exercise: Browsing an existing project (20 min)": [[22, "exercise-0"]], "Exercise: Collaborating within the same repository (25 min)": [[3, "exercise-0"], [4, "exercise-0"]], "Exercise: Copy and browse an existing project": [[22, "exercise-copy-and-browse-an-existing-project"]], "Exercise: Creating branches and commits": [[21, "exercise-creating-branches-and-commits"]], "Exercise: Licensing situations": [[18, "exercise-2"]], "Exercise: Merging branches": [[24, "exercise-0"]], "Exercise: Practice creating commits and branches (20 min)": [[21, "exercise-0"]], "Exercise: Practicing code review (25 min)": [[1, "exercise-0"]], "Exercise: Practicing profiling": [[15, "exercise-0"]], "Exercise: Set up a Sphinx documentation": [[7, "exercise-set-up-a-sphinx-documentation"], [7, "exercise-0"]], "Exercise: Turn your project to a Git repo and share it (20 min)": [[27, "exercise-0"]], "Exercise: What constitutes derivative work?": [[18, "exercise-1"]], "Exercises": [[15, "exercises"], [19, "exercises"]], "Features: roll-back, branching, merging, collaboration": [[25, "features-roll-back-branching-merging-collaboration"]], "Follow the PEP 8 style guide": [[9, "follow-the-pep-8-style-guide"]], "Forking a repository": [[2, "forking-a-repository"]], "Forking, cloning, and browsing": [[22, null]], "From classes to functions": [[17, "from-classes-to-functions"]], "GitHub, VS Code, or command line": [[22, "github-vs-code-or-command-line"]], "Great resources": [[16, null]], "Help and discussion": [[1, "help-and-discussion"], [3, "help-and-discussion"]], "Help! I don\u2019t have permissions to push my local changes": [[3, "help-i-don-t-have-permissions-to-push-my-local-changes"]], "How about staging and committing?": [[26, "how-about-staging-and-committing"]], "How large should a commit be?": [[26, "how-large-should-a-commit-be"]], "How testing is often taught": [[19, "how-testing-is-often-taught"]], "How to approach other people\u2019s repositories with ideas, changes, and requests": [[3, "how-to-approach-other-peoples-repositories-with-ideas-changes-and-requests"]], "How to ask for changes in a pull request": [[1, "how-to-ask-for-changes-in-a-pull-request"]], "How to avoid: \u201cIt works on my machine \ud83e\udd37\u201d": [[6, "how-to-avoid-it-works-on-my-machine"]], "How to cite software": [[16, "how-to-cite-software"]], "How to communicate the dependencies as part of a report/thesis/publication": [[6, "how-to-communicate-the-dependencies-as-part-of-a-report-thesis-publication"]], "How to contribute changes to repositories that belong to others": [[3, null]], "How to create a fork": [[22, null]], "How to design your code before writing it": [[17, "how-to-design-your-code-before-writing-it"]], "How to get a digital object identifier (DOI)": [[12, "how-to-get-a-digital-object-identifier-doi"]], "How to install dependencies into environments": [[6, "how-to-install-dependencies-into-environments"]], "How to make your software citable": [[16, "how-to-make-your-software-citable"]], "How to modify a pull request to address the review comments": [[1, "how-to-modify-a-pull-request-to-address-the-review-comments"]], "How to publish your code": [[16, null]], "How to suggest a change in a pull request as a reviewer": [[1, "how-to-suggest-a-change-in-a-pull-request-as-a-reviewer"]], "How to update your fork with changes from upstream": [[3, "how-to-update-your-fork-with-changes-from-upstream"]], "How to verify your installation": [[11, "how-to-verify-your-installation"]], "If you have a tool/method that works for you, keep using it": [[11, null]], "In-code documentation": [[7, "in-code-documentation"]], "Installing Python via Miniforge": [[11, "installing-python-via-miniforge"]], "Installing and activating the software environment": [[11, "installing-and-activating-the-software-environment"]], "Instructor note": [[7, "instructor-note-0"], [7, "instructor-note-1"], [8, "instructor-note-0"], [13, "instructor-note-0"], [19, "instructor-note-0"], [22, "instructor-note-0"]], "Introduction to version control with Git and GitHub": [[20, null]], "Is putting software on GitHub/GitLab/\u2026 publishing?": [[16, "is-putting-software-on-github-gitlab-publishing"], [27, "is-putting-software-on-github-gitlab-publishing"]], "Iterating": [[9, "iterating"], [9, "id1"]], "Keypoints": [[6, "keypoints-0"], [6, "keypoints-1"], [7, "keypoints-0"]], "Know your collections": [[9, "know-your-collections"]], "Learning goals": [[8, null]], "Linters and formatters can be configured to your liking": [[9, "discussion-0"]], "Linting and static type checking": [[9, "linting-and-static-type-checking"]], "List comprehensions, map, and filter instead of loops": [[9, "list-comprehensions-map-and-filter-instead-of-loops"]], "Making functions more ergonomic": [[9, "making-functions-more-ergonomic"]], "Managing dependencies on a supercomputer": [[6, "managing-dependencies-on-a-supercomputer"]], "Many languages allow \u201cdocstrings\u201d": [[7, "many-languages-allow-docstrings"]], "Many tools exist": [[15, "many-tools-exist"]], "Measure instead of guessing": [[15, "measure-instead-of-guessing"]], "Merging changes and contributing to the project": [[24, null]], "Monday": [[10, "monday"]], "More exercises": [[18, "more-exercises"]], "More resources": [[16, "more-resources"], [18, "more-resources"]], "Motivation": [[3, null], [3, null], [19, "motivation"], [25, null]], "Objectives": [[2, "objectives-0"], [6, "objectives-0"], [7, "objectives-0"], [9, "objectives-0"], [12, "objectives-0"], [14, "objectives-0"], [15, "objectives-0"], [16, "objectives-0"], [18, "objectives-0"], [19, "objectives-0"], [21, "objectives-0"], [22, "objectives-0"], [24, "objectives-0"], [25, "objectives-0"], [27, "objectives-0"]], "Often a README is enough - checklist": [[7, "often-a-readme-is-enough-checklist"]], "Often you can avoid using indices": [[9, "often-you-can-avoid-using-indices"]], "One of the simplest tools is to insert timers": [[15, "one-of-the-simplest-tools-is-to-insert-timers"]], "Opening a pull request towards the upstream repository": [[3, "opening-a-pull-request-towards-the-upstream-repository"]], "Optional: How to auto-generate API documentation in Python": [[7, "optional-how-to-auto-generate-api-documentation-in-python"]], "Optional: How to turn your project to a Git repo and share it": [[27, null]], "Other useful tooling for notebooks": [[13, null]], "Papers with focus on scientific software": [[16, "papers-with-focus-on-scientific-software"]], "Parallelizing": [[9, "parallelizing"]], "Pinning package versions": [[6, "pinning-package-versions"]], "Possibilities to host Sphinx documentation": [[7, "possibilities-to-host-sphinx-documentation"]], "Practical advice: How much Git is necessary?": [[26, null]], "Practicing code review": [[1, null]], "Prefer catch-all unpacking over indexing/slicing": [[9, "prefer-catch-all-unpacking-over-indexing-slicing"]], "Preparation": [[7, "prerequisites-0"], [10, "prerequisites-0"]], "Profiling": [[15, null]], "Project structure": [[9, "project-structure"]], "Pull requests can be coupled with automated testing": [[3, "pull-requests-can-be-coupled-with-automated-testing"]], "Pure functions": [[17, "pure-functions"]], "PyPI": [[6, null]], "Pytest": [[19, "pytest"]], "Python, conda, anaconda, miniforge, etc?": [[11, null]], "Reading and writing files": [[9, "reading-and-writing-files"]], "Recovery": [[3, "solution-0"]], "Removing the software environment": [[11, "removing-the-software-environment"]], "Reproducible environments and dependencies": [[6, null]], "Reproducible research software development using Python (ML example)": [[10, null]], "Resolving a conflict (demonstration)": [[23, "resolving-a-conflict-demonstration"]], "Schedule": [[10, "schedule"]], "Searching in a forked repository will not work instantaneously!": [[22, null]], "Sharing dynamic notebooks using Binder": [[12, "sharing-dynamic-notebooks-using-binder"]], "Sharing notebooks": [[12, null]], "Should we even optimize the code?": [[15, "should-we-even-optimize-the-code"]], "Software install instructions": [[11, null]], "Solution": [[15, "solution-0"], [18, "solution-0"], [18, "solution-1"], [25, "solution-0"]], "Solution and hints": [[4, "solution-and-hints"]], "Solution and walk-through": [[21, "solution-and-walk-through"], [22, "solution-and-walk-through"], [24, "solution-and-walk-through"]], "Starting JupyterLab": [[11, "starting-jupyterlab"]], "Starting questions for the collaborative document": [[17, "starting-questions-for-the-collaborative-document"]], "Step 1: Initialize the package metadata and try a local install": [[14, "step-1-initialize-the-package-metadata-and-try-a-local-install"]], "Step 2: Testing an install from GitHub": [[14, "step-2-testing-an-install-from-github"]], "Step 3: Deploy the package to test-PyPI using GitHub Actions": [[14, "step-3-deploy-the-package-to-test-pypi-using-github-actions"]], "Summary": [[1, "summary"], [3, "summary"], [4, "summary"], [21, "summary"], [22, "summary"], [24, "summary"]], "Synchronizing changes between repositories": [[2, "synchronizing-changes-between-repositories"]], "Talking about code": [[25, "talking-about-code"]], "Taxonomy of software licenses": [[18, "taxonomy-of-software-licenses"]], "The human side of conflicts": [[23, "discussion-0"]], "There are many choices and we try to suggest a good compromise": [[11, "discussion-0"]], "This will be a demo": [[14, null]], "Thursday": [[10, "thursday"]], "Tools and distributions for dependency management in Python": [[6, "tools-and-distributions-for-dependency-management-in-python"]], "Tools and useful practices": [[9, null]], "Tracing profilers vs. sampling profilers": [[15, "tracing-profilers-vs-sampling-profilers"]], "Tuesday": [[10, "tuesday"]], "Two ecosystems: PyPI (The Python Package Index) and Conda": [[6, "two-ecosystems-pypi-the-python-package-index-and-conda"]], "Unpacking": [[9, "unpacking"]], "Updating environments": [[6, "updating-environments"]], "Use an auto-formatter": [[9, "use-an-auto-formatter"]], "Use relative paths and pathlib": [[9, "use-relative-paths-and-pathlib"]], "Use subprocess instead of os.system": [[9, "use-subprocess-instead-of-os-system"]], "We will not focus on \u2026": [[8, null]], "Wednesday": [[10, "wednesday"]], "What else is possible": [[19, "what-else-is-possible"]], "What if you have more than just one script?": [[14, "discussion-0"]], "What if you need more than a README?": [[7, "what-if-you-need-more-than-a-readme"]], "What is a protected branch? And how to modify it?": [[4, "what-is-a-protected-branch-and-how-to-modify-it"]], "What level of branching complexity is necessary for each project?": [[26, "what-level-of-branching-complexity-is-necessary-for-each-project"]], "What to avoid": [[26, "what-to-avoid"]], "What we typically like to snapshot": [[25, "what-we-typically-like-to-snapshot"]], "Where to read more": [[7, "where-to-read-more"]], "Where to start": [[19, "where-to-start"]], "Where to start with documentation": [[7, null]], "Why do we need to keep track of versions?": [[25, "why-do-we-need-to-keep-track-of-versions"]], "Why software licenses matter": [[18, "why-software-licenses-matter"]], "Why? \ud83d\udc97\u2709\ufe0f to your future self": [[7, "why-to-your-future-self"]], "Working on the command line? Use \u201cgit status\u201d all the time": [[26, "working-on-the-command-line-use-git-status-all-the-time"]], "Writing useful commit messages": [[26, "writing-useful-commit-messages"]], "Zip if you need to iterate over two collections": [[9, "zip-if-you-need-to-iterate-over-two-collections"]]}, "docnames": ["collaboration", "collaboration/code-review", "collaboration/concepts", "collaboration/forking-workflow", "collaboration/same-repository", "credit", "dependencies", "documentation", "example", "good-practices", "index", "installation", "notebooks/sharing", "notebooks/tooling", "packaging", "profiling", "publishing", "refactoring-concepts", "software-licensing", "testing", "version-control", "version-control/branching-and-committing", "version-control/browsing", "version-control/conflict-resolution", "version-control/merging", "version-control/motivation", "version-control/practical-advice", "version-control/sharing"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["collaboration.md", "collaboration/code-review.md", "collaboration/concepts.md", "collaboration/forking-workflow.md", "collaboration/same-repository.md", "credit.md", "dependencies.md", "documentation.md", "example.md", "good-practices.md", "index.md", "installation.md", "notebooks/sharing.md", "notebooks/tooling.md", "packaging.md", "profiling.md", "publishing.md", "refactoring-concepts.md", "software-licensing.md", "testing.md", "version-control.md", "version-control/branching-and-committing.md", "version-control/browsing.md", "version-control/conflict-resolution.md", "version-control/merging.md", "version-control/motivation.md", "version-control/practical-advice.md", "version-control/sharing.md"], "indexentries": {}, "objects": {}, "objnames": {}, "objtypes": {}, "terms": {"": [1, 4, 6, 11, 12, 15, 16, 17, 18, 19, 21, 22, 23, 24, 26, 27], "0": [7, 8, 9, 16, 17, 18, 19, 21, 26, 27], "00": 10, "04": [7, 14, 19], "08": [16, 27], "09": [10, 25], "0e": 19, "1": [7, 8, 9, 10, 15, 16, 19], "10": [10, 16, 18, 19], "100": [17, 19, 25, 27], "10000000": 9, "100k": 6, "11": [10, 16], "1121": 16, "11554001": 18, "12": [6, 7, 10, 11, 14, 19, 27], "123": 26, "1234": 16, "1257": 16, "13": [9, 10], "14": 10, "15": [7, 10, 19, 26], "1560": 17, "17": 27, "19": 25, "19k": 27, "2": [7, 9, 10, 15, 16, 17, 19, 26, 27], "20": [10, 15, 19], "200": 6, "2013": 7, "2016": 16, "2019": 25, "2019a": 16, "2019b": 16, "2020": 25, "2021": [16, 18, 25], "2023": [11, 25], "2024": 11, "2025": [7, 25], "21": [25, 27], "24": [7, 14, 19], "2600": 15, "273": 7, "2799": 18, "28": 27, "28k": 6, "2d": 10, "2x": 17, "3": [6, 7, 9, 11, 17, 18, 19, 27], "30": [7, 9, 10], "32": [17, 19], "35": 10, "36": 27, "37": 19, "4": [9, 14, 16, 17, 18, 27], "40": 10, "42": 21, "45": 10, "5": [7, 9, 15, 17, 19], "50": [7, 8, 26], "5000": 3, "5281": [16, 18], "555555555": 17, "5678": 16, "6": [9, 19, 23, 27], "7": [8, 9, 27], "77160": 18, "777777": 19, "79ce3b": 22, "79ce3be8": 25, "8": [15, 17], "9": [3, 9, 16, 17, 19], "90": 15, "9101": 16, "94": 8, "A": [1, 2, 4, 6, 9, 10, 14, 16, 18, 19, 21, 22, 23, 26], "And": [1, 9, 18], "As": [1, 9, 14, 15, 17, 18], "At": [6, 7, 12, 22], "BY": [5, 16, 18], "Be": [2, 4, 8, 27], "Being": [1, 3, 4, 6, 24], "But": [1, 3, 4, 9, 15, 22, 24, 25, 26, 27], "By": [6, 18, 22, 24], "For": [4, 6, 7, 9, 11, 15, 18, 21, 27], "If": [1, 3, 4, 6, 7, 9, 10, 12, 14, 15, 16, 18, 19, 21, 22, 24, 26, 27], "In": [1, 2, 3, 4, 6, 8, 10, 11, 12, 14, 15, 16, 18, 19, 21, 22, 23, 24, 25, 27], "Into": 11, "It": [1, 2, 3, 4, 7, 8, 9, 11, 14, 15, 19, 22, 24, 25, 26, 27], "No": 16, "Not": [6, 7, 15, 18, 21, 24, 26], "On": [9, 21, 22, 23, 24, 26], "One": [8, 24], "Or": [1, 3, 6, 9, 25, 26], "The": [1, 2, 3, 4, 5, 7, 8, 9, 12, 15, 16, 18, 19, 21, 22, 24, 26, 27], "Then": [3, 4, 6, 7, 8, 11, 21, 24, 27], "There": [6, 7, 9, 14, 24], "These": [3, 6, 9, 22, 25], "To": [3, 4, 9, 14, 19, 22, 27], "Will": 9, "With": [9, 21, 25], "__init__": [14, 17], "__main__": [9, 15], "__name__": [9, 15], "__pycache__": 26, "__repr__": 17, "__token__": 14, "__version__": 14, "_build": 7, "_result": 15, "aaltoscicomp": [6, 11, 15], "ab": 19, "abid": 6, "abl": [1, 2, 3, 4, 6, 7, 8, 18, 24, 27], "about": [1, 3, 4, 9, 12, 14, 16, 17, 18, 21, 27], "abov": [1, 8, 11, 15, 18, 19, 21, 22, 24, 27], "absolut": 9, "academia": 6, "accept": [1, 3, 4, 8, 26], "access": [2, 3, 4, 12, 16, 18, 22, 27], "accident": [1, 4, 27], "accord": 9, "account": [2, 3, 4, 10, 14, 22, 27], "accuraci": 8, "achiev": [21, 24], "across": [2, 24], "action": 3, "activ": [4, 6, 15], "actual": [10, 21, 22, 24], "ad": [1, 3, 7, 9, 12, 15, 16, 21, 24], "adapt": [11, 12, 14, 15, 16], "add": [1, 4, 7, 9, 14, 15, 16, 18, 19, 21, 22, 24, 26, 27], "addit": [6, 7, 11, 13, 16, 21, 27], "address": [4, 27], "adipisc": 7, "adjust": [3, 4, 7, 8, 9, 21, 24, 26], "advanc": [22, 24], "advantag": [6, 8, 15], "advic": [10, 20, 21], "affect": [6, 9, 11, 18, 25], "after": [1, 3, 11, 12, 15, 22, 23, 24, 27], "again": [1, 3, 4, 19, 24, 26], "against": 18, "agenda": 18, "agpl": 18, "agreement": 3, "aim": 6, "al": 16, "algorithm": [8, 18], "alia": 22, "alic": 9, "aliqua": 7, "aliquip": 7, "all": [2, 4, 5, 6, 7, 10, 11, 13, 14, 15, 18, 19, 21, 22, 23, 27], "allen": 16, "allianc": 18, "allow": [4, 11, 12, 18, 24], "almost": [7, 17, 18], "along": [6, 10, 22], "alpha": 26, "alreadi": [3, 15, 19, 21, 22, 27], "also": [1, 2, 3, 4, 6, 7, 9, 11, 12, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24, 26, 27], "altair": [6, 7, 11, 18, 19], "altern": [2, 4, 6, 9, 14, 15, 22], "alwai": [1, 3, 11, 17, 18, 24, 25, 26], "am": [9, 25], "ambiti": 26, "amend": 1, "amet": 7, "among": 6, "amount": 15, "an": [1, 3, 6, 7, 10, 12, 13, 15, 16, 17, 18, 19, 21, 23, 24, 25, 27], "anaconda": 6, "analysi": 6, "analyz": 9, "ani": [3, 7, 11, 19, 21, 23, 26, 27], "anim": 7, "annot": [21, 22, 25], "anoth": [7, 11, 14, 18, 21, 22, 24], "answer": [6, 14, 21, 22, 24, 25], "anticip": 25, "anymor": [6, 24], "anyon": [11, 22], "anyth": [18, 24], "apach": 18, "api": 9, "appear": [15, 21], "append": [9, 15], "appli": [8, 10, 18], "applic": 22, "apprentic": 9, "approach": [7, 8, 17], "appropri": 16, "approv": [14, 16], "approxim": 15, "apptain": 6, "ar": [1, 2, 3, 4, 6, 7, 8, 10, 14, 15, 16, 18, 19, 21, 23, 24, 25, 26, 27], "arbitrari": 19, "area": [17, 26], "aren": [21, 22, 24], "argument": [6, 8, 9, 19, 21], "arm": 11, "arm64": 11, "around": [0, 10, 16, 18, 21, 24], "arriv": 17, "arrow": [18, 24, 27], "articl": [4, 6], "ask": [3, 4, 6, 15, 18, 23], "assert": [7, 19], "assign": 16, "assist": 18, "assum": [10, 17, 19, 24], "attach": [2, 6, 21], "attent": 3, "attract": 18, "attribut": 18, "august": [7, 25], "aut": 7, "authent": [1, 3, 4], "author": [7, 14, 16, 18, 22, 25, 26, 27], "autoapi": [6, 7, 11], "autoapi_dir": 7, "autoapi_ignor": 7, "autobuild": [6, 7, 11], "autoflak": 9, "autom": 10, "automat": [1, 2, 3, 4, 7, 9, 11, 19, 21], "avail": [6, 11, 15, 18, 19], "avoid": 22, "awai": [17, 26], "awar": 22, "azur": 12, "b": [7, 9, 15, 17, 18, 19, 27], "back": [2, 15, 19, 21, 22], "backend": 14, "background": 11, "backup": [12, 27], "backward": 9, "bad": [25, 26], "badg": 12, "badli": 9, "base": [2, 8, 9, 11, 12, 15, 19, 24, 26], "bash": [7, 11, 19, 22], "basi": [4, 11, 24], "basic": [21, 24], "basicconfig": 9, "bast": 18, "batch": 15, "becam": 7, "becaus": [15, 16, 24, 26, 27], "becom": 22, "been": [23, 24], "befor": [3, 4, 9, 14, 15, 18, 21, 22, 23, 24, 25, 26], "begin": [7, 9, 10], "beginn": [11, 15], "behind": [3, 8], "being": [3, 9, 12, 18], "belong": [0, 8, 10], "below": [1, 3, 4, 6, 7, 9, 11, 15, 16, 21, 22, 24, 27], "beltran": 16, "best": [9, 17, 18, 23], "better": [1, 6, 7, 9, 11, 15, 19, 22, 25, 26], "between": [4, 6, 9, 21, 23, 25], "bewar": 9, "bias": 18, "big": [8, 14, 17, 22], "bin": 11, "binder": [6, 16], "bioconda": [6, 11], "bisect": 9, "bit": 6, "black": [6, 9, 11, 13], "blame": 22, "blob": [19, 25], "block": [7, 9, 10], "blog": [6, 16], "blogdown": 7, "blue": 11, "bob": 9, "book": [3, 4, 9, 15], "bookdown": 7, "bool": [9, 17], "borrow": 6, "both": [3, 4, 6, 9, 15, 17, 21, 22], "bottleneck": 15, "bottom": [4, 8, 17, 21, 22, 24], "box": 24, "branch": [1, 3, 7, 10, 14, 19, 20, 22, 23, 27], "brand": 27, "break": [9, 10, 19, 25, 26], "brett": 9, "brief": 21, "bring": [15, 24], "brittl": 9, "broke": [19, 25], "brows": [6, 8, 9, 10, 15, 19, 20, 24, 25, 26], "browser": [7, 11, 12, 15, 21, 22], "bsd": 18, "bsd3": 18, "buffer": 9, "bug": [18, 19, 25], "bugfix": 18, "build": [6, 10, 14, 17, 18, 19, 21, 22], "buildapi": 14, "built": 7, "bunch": [22, 27], "bundl": 6, "button": [1, 12, 13, 21, 22, 23, 24, 27], "c": [6, 16, 18, 22], "c7f0e8bfc718be04525847fc7ac237f470add76": 2, "cach": [7, 15, 19], "calcul": 17, "call": [2, 4, 6, 11, 14, 15, 16, 17, 21, 22, 24, 27], "can": [1, 2, 4, 6, 7, 8, 11, 12, 14, 15, 16, 17, 18, 19, 21, 23, 24, 25, 26, 27], "cannot": [4, 18], "caption": 7, "captur": 15, "cargo": 6, "carri": 25, "case": [1, 2, 3, 4, 7, 15, 18, 19, 22, 23, 24, 27], "cast": 9, "cat": 17, "catch": 19, "caus": [6, 23], "cc": [5, 16, 18], "cd": [7, 22], "cell": [11, 13], "celsiu": [7, 19], "central": 2, "certain": [21, 25], "cff": 16, "cffinit": 16, "challeng": [6, 11], "chanc": 23, "chang": [0, 6, 7, 9, 10, 11, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27], "changelog": 26, "channel": [6, 7, 11, 14, 19], "charg": 12, "charli": 9, "chart": 8, "chase": 19, "chat": 9, "check": [3, 4, 6, 7, 12, 15, 19, 24, 27], "checker": [9, 18], "checklist": [4, 16], "checkout": [7, 14, 19], "checksum": 25, "chocol": 9, "choic": [18, 26], "choos": [8, 9, 10, 23, 24, 27], "chose": 27, "chue": 16, "chunk": 9, "ci": [7, 19], "cillum": 7, "circ": 17, "circular": 8, "circumst": 23, "citabl": 27, "citat": [7, 16, 19], "cite": 18, "clark": 16, "class": [8, 22], "classic": 15, "classif": [5, 7, 10, 19, 21, 22, 25], "classifi": [8, 14], "clean": [17, 18], "cleanup": [7, 19], "clear": [3, 16, 18], "clearli": 16, "click": [1, 3, 4, 6, 7, 11, 12, 14, 15, 19, 21, 22, 23, 24, 27], "clone": [3, 4, 7, 10, 20, 25], "close": [1, 3, 4, 11], "cloud": [4, 12], "cluster": 6, "cmakelist": 6, "cocalc": 12, "code": [2, 3, 4, 6, 8, 10, 11, 12, 14, 18, 19, 21, 24, 26, 27], "codecov": 19, "coderefineri": [5, 6, 7, 11, 12], "colab": 12, "collabor": [1, 10, 12, 23, 24, 27], "colleagu": [2, 9], "collect": [4, 11, 17, 22, 26], "colon_f": 7, "com": [3, 4, 5, 6, 7, 11, 14, 19, 21, 22, 25, 26, 27], "combin": [1, 18, 23, 24, 26], "come": [4, 6, 9, 18, 19, 22, 24], "comfort": 22, "comic": 6, "command": [2, 4, 6, 8, 9, 11, 17, 21, 23, 24, 27], "commemor": 2, "comment": [4, 7, 9, 18, 24], "commentari": 7, "commerci": [11, 18], "commiss": 18, "commit": [1, 3, 4, 9, 10, 20, 23, 24, 25, 27], "commodo": 7, "common": [1, 2, 5, 6, 14, 18, 21], "commonli": 11, "commun": [2, 4, 11, 14, 18], "compact": 9, "compani": [6, 11, 18], "compar": [7, 9, 18, 19, 25, 26], "compat": [6, 18], "compil": 18, "complet": [2, 9, 15, 18, 26, 27], "complex": [11, 14], "complic": [6, 19], "compon": [17, 18, 19], "compos": 17, "compress": 27, "comput": [2, 3, 6, 8, 10, 11, 16, 22, 25, 27], "con": [6, 11, 15, 17], "concept": [0, 9, 10], "conceptu": 25, "concret": 14, "conda": [7, 14, 19], "condit": [18, 19], "conf": 7, "confid": 23, "config": 22, "configur": [4, 11, 15, 25, 27], "conflict": [6, 10, 20], "connect": [4, 10], "consectetur": 7, "consequat": 7, "consid": [4, 18, 26, 27], "consist": [9, 27], "construct": [1, 4, 7, 9, 10], "consum": 15, "contact": 18, "contain": [2, 3, 11, 14, 15, 16, 24, 26, 27], "container": 6, "contains_wat": 17, "content": [7, 9, 18], "context": [1, 18, 22, 26], "continu": [1, 22, 24], "contribut": [0, 2, 4, 7, 10, 18, 20], "control": [7, 9, 10, 18, 21, 22, 24, 25, 27], "convent": 26, "converg": 26, "convert": [6, 7, 9, 11, 12, 19], "coordin": 9, "copi": [2, 4, 7, 11, 18, 21, 25, 27], "copyleft": 18, "copyright": [4, 7], "corner": 4, "correct": [3, 27], "correctli": [8, 17], "correspond": [14, 19, 22], "cost": 21, "could": [1, 2, 4, 11, 18, 21, 22, 27], "count": [15, 27], "count_unique_word": 9, "count_unique_words1": 15, "count_unique_words2": 15, "count_unique_words3": 15, "counter": 1, "coupl": 7, "cours": [6, 10, 11, 15, 22], "cov": 19, "cover": [7, 24], "coverag": 19, "coveral": 19, "cprofil": 15, "cpu": 15, "crash": 11, "cream": 9, "creat": [1, 2, 3, 6, 7, 9, 10, 11, 12, 15, 16, 18, 19, 20, 23, 25, 26, 27], "creation": 6, "creativ": 18, "creator": 16, "credit": 16, "cross": [3, 26], "crowd": 15, "csv": [8, 9], "culpa": 7, "cupidatat": 7, "current": [6, 15, 21, 24, 27], "custom": 18, "cycl": 9, "cyclic": 9, "d": [16, 18, 24], "dai": [4, 15, 19, 23, 26], "danger": [26, 27], "dashboard": 12, "dask": 9, "data": [2, 8, 9, 15, 17, 18, 19, 23, 24, 25], "dataclass": [9, 17], "date": [3, 16, 23, 25], "david": 9, "db": 7, "deal": 6, "debrief": 10, "decad": 26, "decid": [2, 23], "declar": 23, "decor": [21, 22], "deepnot": 12, "def": [7, 9, 15, 17, 19], "default": [3, 6, 9, 18, 21, 22, 24, 25, 27], "defaultdict": 9, "defin": [3, 6, 21, 22], "definit": 6, "degre": 7, "delai": 15, "delet": [4, 16], "deliber": 1, "delta": 27, "demo": 10, "demonstr": [3, 8, 9, 12, 22], "depend": [4, 7, 9, 10, 11, 14, 15, 18, 19, 22], "deploi": [7, 10, 19], "deprec": 9, "deprecationwarn": 9, "dequ": 9, "describ": [3, 4, 11, 16, 19, 22, 24], "descript": [4, 6, 14, 24, 27], "deserunt": 7, "design": [10, 11, 18, 19, 21, 22, 24, 26], "desk": 9, "detail": [1, 3, 7, 8, 15, 18, 26], "detect": [9, 15, 26], "determinist": 15, "develop": [2, 6, 16, 17, 21, 24, 26], "diataxi": 7, "dict": [9, 17], "dictionari": 9, "did": [6, 16, 21, 25], "didn": [15, 21], "diff": 21, "differ": [2, 3, 6, 8, 9, 11, 15, 17, 18, 21, 22, 23, 24, 25, 27], "difficult": [3, 9, 11, 19, 24], "digit": 16, "dimens": 15, "direct": [3, 4, 7, 24], "directli": [1, 4, 6, 21], "director": 18, "directori": [6, 7, 14, 19, 22, 25, 26, 27], "disadvantag": 15, "discard": 25, "discov": 25, "discuss": [2, 6, 7, 9, 10, 12, 17, 19, 22, 23, 24, 26], "disk": [6, 21], "distribut": [8, 11, 18], "diverg": 21, "divers": 9, "divid": 2, "do": [1, 2, 3, 4, 6, 7, 9, 10, 11, 15, 16, 17, 18, 19, 21, 22, 23, 24, 26, 27], "doc": [6, 7, 12, 25], "doconc": 12, "docstr": [9, 14], "doctor": 15, "document": [6, 10, 16, 21, 25], "doe": [3, 6, 7, 9, 11, 14, 15, 16, 17, 18, 19, 21, 23], "doesn": [11, 15], "doi": [16, 18, 27], "dolor": 7, "don": [1, 4, 6, 7, 9, 10, 11, 15, 18, 21, 22, 23, 24, 27], "donald": 15, "done": [3, 4, 9, 21, 22, 24, 27], "dot": [21, 22, 24], "doubl": 3, "down": [4, 12, 15, 17, 24, 27], "download": [2, 6, 11, 15, 18], "draft": [18, 24], "drive": [6, 12], "driven": 17, "driver": 18, "drop": 6, "ds_store": 7, "due": 6, "dui": 7, "duplic": 9, "durat": 15, "dure": [1, 3, 6, 11, 18, 23, 26], "dynam": [14, 18], "e": [2, 6, 7, 9, 12, 15, 16, 18, 21, 22], "e86": 16, "ea": 7, "each": [1, 2, 3, 4, 6, 8, 11, 21, 22], "earli": [4, 23, 26], "earlier": 17, "eas": 6, "easi": [1, 6, 14, 16, 17, 18, 21, 22, 27], "easier": [6, 7, 9, 14, 17, 19, 24, 26], "easiest": 18, "easili": [6, 21, 26], "edit": [12, 16, 21], "editor": [9, 10, 22, 23, 24], "effect": [9, 17, 24], "effici": 8, "effort": [4, 15], "egi": 12, "either": [1, 6, 9, 12, 21, 22, 27], "eiusmod": 7, "el": [7, 19], "elabor": 9, "element": [9, 15], "elit": 7, "els": [1, 4, 9, 18, 23, 26], "email": [4, 14], "employ": 18, "employe": 6, "empti": [26, 27], "emul": 7, "en": 4, "enabl": [1, 26], "encc": 15, "encod": [9, 15], "encourag": [4, 7, 21, 22, 24], "end": [4, 9, 26], "enemi": 26, "enforc": 9, "engin": [10, 26], "enim": 7, "enjoi": 12, "enough": [9, 16, 21, 22, 24, 26], "ensur": [6, 16], "enter": [12, 15, 21, 22], "entir": [14, 21], "entri": 14, "enumer": 27, "env": [6, 11, 14], "envion": 14, "environ": [7, 10, 12, 15, 19], "episod": [1, 3, 4, 6, 7, 14, 21, 22, 24], "epub": 15, "equal": 24, "equat": 7, "equival": 6, "error": [3, 7, 9, 11, 19, 21, 27], "especi": [9, 26], "ess": 7, "essenti": 6, "est": 7, "estim": 15, "et": [7, 16], "etc": [18, 26], "ethic": 9, "eu": [7, 18], "eupl": [14, 18], "europa": [17, 18], "european": [14, 18], "ev": 9, "evalu": 22, "even": [1, 4, 6, 11, 19, 21, 27], "even_numb": 9, "even_numbers1": 9, "even_numbers2": 9, "event": 15, "event_nam": 7, "everi": [7, 11, 15, 19, 22], "everybodi": 11, "everyon": [2, 23], "everyth": [7, 11, 17], "everywher": 11, "evil": 15, "evolv": 7, "ex": [7, 11], "exact": 15, "exactli": 3, "exam": 10, "exampl": [3, 4, 5, 6, 7, 9, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 25, 26, 27], "excel": 9, "excepteur": 7, "exclude_pattern": 7, "execut": [12, 15, 27], "exercit": 7, "exist": [3, 6, 18, 21, 25, 27], "exit": [8, 15], "expect": [3, 8, 18, 19, 21], "expected_result": 19, "expens": 9, "experi": [7, 14, 19, 25], "experienc": 15, "expert": [4, 27], "explain": [7, 9, 15, 18, 27], "explan": [15, 19, 27], "explor": [7, 12, 22], "export": [6, 14], "express": 17, "extend": [7, 18, 19], "extens": [7, 21, 22], "extern": [6, 7, 9], "extra": [1, 7, 11], "f": [6, 9, 11, 15, 17, 18], "f1000research": 16, "f_to_c_factor": 17, "f_to_c_offset": 17, "face": 6, "fact": 1, "fahrenheit": 19, "fahrenheit_to_celsiu": [17, 19], "fahrenheit_to_celsius_bad": 17, "fail": [3, 19], "fair": 16, "fals": [7, 17, 19], "famili": 16, "familiar": [1, 3, 4, 9, 18], "fast": [6, 15], "faster": [6, 11, 12, 15, 26], "favorit": 10, "favorite_ice_cream": 9, "favourit": [17, 26], "featur": [3, 21, 26], "februari": 25, "feedback": [3, 4, 10, 24], "feel": [7, 19], "fetch": [2, 3, 4, 6], "few": [1, 2, 3, 9, 14, 15, 19, 22, 24, 26, 27], "fewer": [6, 9], "field": 1, "file": [1, 2, 4, 6, 7, 8, 11, 12, 14, 15, 16, 19, 23, 25, 26, 27], "file_path": [9, 15], "filenam": [21, 22], "fill": 18, "final": [7, 8, 14, 16, 21, 27], "find": [7, 15, 18, 21, 22, 24, 27], "findabl": [16, 27], "findal": [9, 15], "fine": 27, "finish": [4, 15, 27], "first": [2, 3, 4, 6, 7, 8, 11, 12, 14, 15, 17, 19, 21, 22, 24, 26, 27], "firstnam": 14, "fix": [1, 3, 4, 9, 18, 19, 26], "flake8": 9, "flask": 26, "flavor": [7, 9], "flexibl": 14, "flit": [6, 11], "flit_cor": 14, "flit_index_url": 14, "flit_password": 14, "flit_usernam": 14, "float": [7, 17], "fly": 6, "focu": 6, "focus": [14, 16], "folder": [12, 22, 27], "follow": [5, 6, 7, 8, 10, 11, 12, 14, 15, 16, 21, 22, 25, 27], "forc": [4, 6, 27], "force11": 16, "force_orphan": 7, "forg": [6, 7, 11, 14, 19], "forget": [4, 7, 24], "forgot": [6, 24], "forgotten": 18, "forgotten_vari": 9, "fork": [7, 10, 20, 24, 25], "form": [4, 16, 21, 24, 26], "format": [9, 16], "formatt": [6, 11, 13], "former": 2, "formerli": 12, "fortran": 6, "found": [9, 11, 25], "fr": 7, "fragment": 1, "free": [6, 7, 9, 11, 12, 26], "freeli": 11, "freez": 6, "frequent": 21, "from": [1, 2, 4, 6, 7, 9, 10, 11, 12, 15, 16, 18, 19, 21, 22, 23, 24, 25, 26, 27], "frustrat": 24, "fugiat": 7, "fulli": [12, 26, 27], "fun": [3, 26], "function": [7, 14, 15, 18, 19, 21, 22], "futur": [4, 6, 8, 12, 18, 26], "g": [2, 6, 7, 9, 12, 15, 17, 18, 21, 22], "gain": 11, "gate": 1, "gener": [2, 6, 8, 9, 11, 12, 15, 18, 19, 21, 26], "generate_data": [8, 14], "generate_predict": [8, 22, 25], "get": [1, 2, 3, 4, 7, 8, 10, 14, 15, 16, 18, 19, 21, 22, 23, 24, 26, 27], "ggplot2": 26, "gh": 7, "gist": 12, "git": [2, 3, 4, 6, 7, 10, 14, 16, 21, 22, 23, 24, 25], "github": [1, 2, 3, 5, 6, 10, 11, 12, 15, 21, 23, 24, 25], "github_token": 7, "githubusercont": [6, 11], "gitignor": [21, 26, 27], "gitlab": [1, 2, 3, 4, 7, 19, 24, 25], "give": [1, 4, 10, 11, 15, 25], "given": [15, 16, 17, 19, 21], "global": [6, 17, 22, 23], "go": [4, 7, 9, 10, 11, 14, 15, 21, 22, 25, 27], "goal": [1, 19], "goe": [18, 21, 22, 24], "gone": [24, 25], "gonzalez": 16, "good": [1, 4, 6, 7, 9, 12, 15, 18, 19, 26, 27], "googl": [12, 25], "gopher": [21, 25], "got": 18, "gpl": 18, "gpu": 15, "grab": 3, "grant": 3, "graph": [21, 25], "graphic": 11, "great": [12, 18, 25, 26], "green": [24, 27], "grep": [17, 22], "group": [1, 4, 10, 16, 18], "grow": [9, 15], "guarante": [16, 19], "guid": [7, 16, 18, 19], "guidelin": 18, "guppi": 15, "gutenberg": 15, "h": 18, "ha": [2, 4, 5, 6, 8, 9, 11, 12, 18, 21, 22, 23, 24, 26], "had": 9, "half": 4, "hand": 10, "handl": 9, "happen": [4, 21, 27], "happi": 23, "hard": [14, 26], "harder": [1, 6], "hash": 21, "have": [1, 2, 4, 6, 7, 9, 10, 12, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 27], "head": 7, "heapi": 15, "heapq": 9, "hear": 4, "help": [4, 6, 8, 9, 15, 19, 23, 26, 27], "helper": 9, "here": [1, 3, 4, 6, 11, 14, 15, 17, 18, 19, 21, 23, 24, 27], "high": [2, 10], "higher": 15, "highlight": [7, 9, 19], "hint": [1, 21, 22, 24], "histor": 22, "histori": [1, 2, 5, 6, 16, 27], "hold": 19, "home": [6, 14], "hong": 16, "hopefulli": [7, 11, 25], "host": [6, 11, 12, 25], "hous": 6, "hover": 22, "how": [0, 2, 8, 9, 10, 14, 15, 18, 20, 21, 23, 24, 25], "howev": [4, 11, 18], "html": [7, 12, 15], "html_theme": 7, "http": [3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 21, 22, 25, 26, 27], "huge": [9, 11, 26], "hugo": 7, "human": 4, "hypothesi": 19, "i": [1, 2, 5, 6, 8, 9, 10, 11, 12, 14, 18, 20, 21, 22, 23, 24, 25], "ic": 9, "icecream": [6, 9, 11], "icon": [4, 7, 11, 21, 27], "id": [7, 12], "idea": [6, 12, 25, 26], "ideal": 4, "identifi": [2, 9, 16, 21, 22, 27], "ignor": [7, 9], "illustr": [21, 22, 24], "imag": [3, 6, 7, 15, 21, 25], "immut": 9, "imperfect": 26, "implement": [1, 2, 9, 11], "import": [6, 8, 9, 10, 11, 12, 15, 17, 18, 21, 22, 24, 26], "imposs": [7, 17], "improv": [1, 6, 7, 9, 10, 15, 21, 26], "impur": 17, "inbox": 4, "inc": 6, "incididunt": 7, "includ": [4, 6, 7], "incompat": 9, "incomplet": 1, "incorpor": 18, "increas": [15, 26], "incredibli": 4, "independ": [2, 7, 9, 10], "index": [7, 14, 22], "indic": [3, 18], "individu": 10, "info": [1, 9], "inform": [3, 4, 9, 12, 15, 16, 18, 21, 22, 24, 26], "informat": 18, "infrequ": 15, "ingredi": 3, "init": [6, 7, 14, 19, 27], "initi": [11, 27], "input": [9, 15, 17], "ins": 18, "insensit": [4, 15, 22], "insid": [6, 9, 17, 22], "insight": [21, 22, 25], "inspect": 22, "inspir": [9, 21, 25, 26], "instal": [4, 7, 9, 10, 15], "instanc": [2, 15, 18, 22], "instead": [1, 4, 6, 7, 19, 22, 27], "institut": [6, 18], "instruct": [3, 4, 6, 7, 10, 21], "instructor": [3, 4, 11, 12], "insur": 18, "int": [9, 15], "integ": 8, "integer_numb": 9, "integr": 7, "intent": 2, "interact": [4, 12], "interchang": [4, 21], "interest": [1, 4, 22, 24], "interf": 25, "interfac": [1, 4, 6, 11, 21, 27], "interfer": [11, 21], "intern": [7, 8], "interpret": 9, "introduc": 1, "introduct": 10, "invent": 7, "invit": 4, "io": [6, 9, 11, 12, 13, 15], "ipsum": 7, "ipynb": 12, "irur": 7, "is_even": 9, "isn": 1, "isol": 11, "isort": [6, 9, 11, 13], "issu": [3, 18, 22, 23, 26], "itertool": 9, "its": [6, 12], "itself": 7, "jane": 16, "januari": 25, "jargon": 11, "jekyl": 7, "job": [7, 14, 19], "john": 7, "join": 19, "joinup": 18, "joss": 7, "journal": [16, 18], "journei": 15, "juli": 18, "julia": [6, 26], "jupinx": 12, "jupit": 16, "jupyt": [9, 11, 12, 16, 21], "jupyterlab": [6, 9, 12, 13], "jupyterlit": 12, "just": [1, 3, 4, 7, 22, 23, 24, 26, 27], "k": 16, "katz": 16, "keep": [1, 3, 7, 15, 17, 18, 23], "kei": [7, 9], "kelvin": 7, "kelvin_to_celsiu": 7, "kept": 7, "keyword": [4, 9, 22], "kib": 27, "kill": 19, "kilomet": 17, "kind": [1, 3, 4, 25], "km": 17, "knitr": 7, "know": [1, 3, 4, 6, 11, 12, 15, 17, 18, 19, 22, 24, 25, 26], "knuth": 15, "l": 27, "l25": 25, "l28": 25, "lab": 11, "label": [8, 24], "labor": 7, "laboratori": 6, "labori": 7, "laborum": 7, "lack": [9, 18], "languag": [6, 9, 10, 11, 17, 18, 19], "laptop": [2, 10, 25], "larg": [9, 15, 27], "larger": [1, 15], "largest": [6, 9], "last": [14, 24], "lastnam": 14, "later": [1, 2, 3, 4, 6, 7, 8, 12, 14, 18, 21, 22, 24, 25, 26], "latest": [2, 6, 25], "latex": [12, 25], "launch": [9, 12], "layout": [23, 26], "lead": 10, "learn": [1, 2, 3, 4, 7, 9, 12, 14, 15, 19, 24, 26], "learner": [4, 7], "least": [1, 7, 18], "leav": [14, 18, 27], "left": [4, 21, 22], "legaci": 14, "legal": 9, "len": [9, 15], "leo": 15, "less": [9, 14, 15, 19], "lesson": [1, 3, 5, 7, 12, 16, 18, 23], "let": [3, 6, 7, 8, 9, 12, 18, 21, 26, 27], "level": [2, 9], "leverag": 7, "librari": [6, 9, 11, 12, 18], "licenc": [14, 18], "licens": [4, 6, 7, 10, 14, 16, 22, 27], "lightweight": 6, "like": [1, 2, 3, 4, 6, 7, 8, 11, 14, 16, 18, 19, 21, 22, 24, 26, 27], "limit": [7, 11], "line": [1, 2, 3, 4, 8, 9, 11, 14, 15, 19, 21, 23, 24, 27], "line_profil": 15, "linear": [8, 22], "link": [3, 4, 7, 9, 15, 18, 27], "linkcheck": 7, "linux": 11, "list": [6, 11, 15, 16, 22, 25], "littl": 24, "live": [7, 12, 23], "ll": [6, 21, 22], "local": [1, 4, 6, 7, 19, 22, 24, 27], "locat": [9, 15, 21], "lock": [6, 9, 18], "log": [9, 15, 16, 21, 22, 24, 27], "login": 27, "long": [6, 9, 22, 23], "longer": [6, 26], "look": [1, 3, 6, 7, 8, 9, 11, 12, 14, 18, 19, 21, 22], "lookup": 15, "lorem": 7, "lose": 26, "lot": [7, 9, 11, 25, 27], "low": 7, "lower": [9, 15], "lunch": 10, "m": [6, 16, 21, 27], "maco": 11, "macosx": 11, "made": [2, 4, 6, 18], "magna": 7, "magnifi": 22, "mai": [7, 18, 21, 26], "main": [1, 2, 3, 6, 7, 9, 11, 14, 15, 19, 22, 23, 24, 26, 27], "mainli": 4, "maintain": [4, 6, 11, 15, 17], "major": [6, 8, 19, 21], "majority_index": [22, 25], "make": [1, 2, 3, 7, 8, 14, 15, 17, 18, 19, 21, 22, 23, 24, 25, 26, 27], "makefil": 6, "mamba": [6, 7, 11, 19], "manag": [4, 9, 11, 18], "mani": [4, 6, 9, 14, 24, 26], "manual": 9, "manuscript": [21, 25], "mar": 27, "mark": [4, 21, 26], "markdown": 12, "marker": 23, "markup": 7, "mashup": 5, "mass": 9, "master": [2, 21, 27], "match": [9, 15, 19, 22], "materi": [3, 5, 16, 18, 19, 22, 24, 25], "math": [7, 17], "mathemat": 17, "matrix": 15, "maxdepth": 7, "maximum": 8, "mayb": [3, 18], "md": [7, 12, 21], "me": [16, 21, 25, 26], "mean": [1, 2, 4, 11, 17, 21, 23, 24, 27], "meaning": 21, "meant": [4, 22, 24], "measur": [7, 23], "mechan": [1, 2], "medic": 15, "member": 9, "memori": [9, 15], "memory_profil": 15, "mention": [4, 6, 18], "mentor": 9, "merg": [1, 2, 3, 4, 10, 20, 21, 22, 23, 26], "messag": [4, 8, 9, 16, 21, 22, 25], "met": 19, "meta": 9, "metadata": [16, 21, 25], "method": [21, 27], "mib": 27, "micromamba": [6, 7, 19], "micropipenv": 6, "microsoft": [9, 12], "middl": 9, "midjournei": 18, "might": [3, 9, 15, 22, 23, 24, 26, 27], "mileston": 26, "min": [7, 10, 19], "mind": [15, 27], "miniatur": 6, "miniconda": [6, 11], "miniforg": 6, "miniforge3": 11, "minim": [6, 7, 11], "minimum": [8, 12], "minor": 3, "mint": 9, "minu": 1, "minut": [9, 12, 15, 22], "miss": 15, "mistak": 1, "mit": 18, "mkdoc": 7, "ml": [6, 11], "model": 12, "modern": [3, 6, 9, 14], "modif": [3, 7, 21], "modifi": [6, 9, 18, 23, 24], "modul": [6, 9, 14, 19], "modular": [10, 23], "mollit": 7, "monopoli": 9, "month": 23, "moon": 17, "more": [1, 6, 11, 19, 21, 22, 23, 25, 26, 27], "most": [4, 6, 8, 9, 10, 14, 15, 18, 21, 22, 24, 26], "motiv": [10, 20, 26], "motto": 14, "mous": 3, "move": [10, 14, 19, 21], "mpi4pi": 9, "mri": 15, "msys2": 6, "much": [1, 3, 4, 6, 7, 9, 10, 11, 15, 19, 20, 21, 22, 25], "multipl": [2, 9, 21, 22, 26], "multiprocess": 9, "music": 18, "must": [4, 18], "mutant": 19, "mutat": 19, "mutmut": 19, "my": [4, 16, 18, 25, 26, 27], "mybind": 12, "myenv": 6, "mypi": 9, "myproject": [25, 27], "myscript": 27, "myst": [6, 11], "myst_enable_extens": 7, "myst_pars": 7, "myvers": 25, "n": [6, 11, 15, 16], "name": [4, 6, 7, 8, 9, 11, 14, 16, 17, 19, 21, 22, 24, 27], "namedtupl": 17, "nation": 6, "navig": [3, 11, 22], "nbconvert": 12, "nbsphinx": 12, "nbviewer": 12, "nc": 18, "nd": 18, "nearest": 10, "necessari": [1, 2, 4, 7, 10, 18, 20, 21], "need": [1, 2, 4, 6, 10, 11, 12, 13, 16, 18, 19, 21, 22, 24, 26, 27], "neg": 7, "neighbor": 10, "neil": 16, "network": [6, 21, 24, 25], "never": [18, 26, 27], "new": [1, 2, 3, 6, 7, 11, 14, 15, 17, 19, 22, 23, 24, 26, 27], "newaddress": 4, "newer": 6, "newfeatur": 25, "next": [1, 6, 9, 14, 19, 21, 22, 23, 24, 27], "nice": [4, 6, 11, 17, 21, 22], "niemey": 16, "nisi": 7, "non": [6, 7, 8, 18], "none": [9, 27], "normal": [8, 21], "nostrud": 7, "note": [3, 4, 12, 15, 21, 24, 26, 27], "notebook": [6, 9, 10, 11, 16, 19, 21], "notedown": 12, "noth": [16, 19], "notic": [3, 24], "notif": 4, "notifi": [4, 15], "notion": 17, "now": [1, 3, 4, 6, 7, 12, 14, 16, 17, 18, 21, 22, 23, 24, 25, 26, 27], "nowadai": 6, "nulla": 7, "num": 8, "num_neighbor": 21, "number": [1, 3, 4, 6, 8, 9, 15, 16, 21], "numer": 8, "numpi": [6, 7, 11, 14, 18, 19, 26], "o": 15, "oauth": 27, "object": 17, "observ": [3, 4, 7], "obtain": 14, "occaecat": 7, "occurr": 22, "odd": 21, "off": [9, 21], "offer": [6, 9, 12, 22, 24, 27], "offic": 18, "officia": 7, "often": [2, 4, 6, 15, 18, 21, 23, 26], "oh": 25, "ok": 24, "old": [6, 14, 19], "older": 27, "onc": [1, 3, 4, 9, 10, 11, 19, 21, 23, 26, 27], "one": [1, 2, 3, 4, 6, 7, 9, 10, 15, 17, 18, 19, 21, 22, 23, 24, 26, 27], "onelin": [21, 22, 24], "ones": 24, "onli": [1, 4, 6, 7, 8, 9, 12, 15, 18, 21, 22, 23, 24, 26, 27], "onlin": 27, "oop": 26, "open": [1, 6, 7, 11, 15, 18, 21, 22, 23, 24, 27], "oper": [2, 6, 8, 9, 11, 15], "opinion": 7, "optim": [9, 17], "option": [6, 8, 10, 11, 20, 21, 22, 24], "orcid": 16, "order": [1, 2, 7, 9], "org": [7, 12, 14, 15, 16, 18, 19], "organ": [4, 9, 14, 22], "orient": 17, "origin": [3, 4, 5, 18, 22, 27], "osi": 14, "other": [0, 1, 2, 4, 6, 7, 8, 9, 10, 11, 14, 18, 21, 22, 23, 24, 25, 26, 27], "otherwis": [1, 15, 26], "our": [1, 4, 7, 8, 12, 14, 16, 18, 19, 21, 22, 24, 25, 27], "ourselv": 15, "out": [1, 3, 7, 13, 15, 18, 21, 22, 25, 26], "output": [7, 8, 9, 22], "outsid": 17, "over": [6, 15, 21, 22, 24, 26], "overhead": 15, "overview": [7, 11, 15, 21, 24], "overwrit": 27, "own": [2, 4, 6, 7, 8, 10, 11, 18, 19, 21, 22, 23, 24, 25, 27], "owner": 18, "p": [16, 18, 26], "pack": 27, "packag": [7, 9, 10, 11, 12, 13, 18], "package_nam": 14, "page": [4, 7, 11, 12, 15, 21, 22, 26, 27], "pai": 3, "paid": 12, "pair": [9, 22], "panda": [6, 7, 11, 14, 18, 19, 26], "paper": [2, 12, 17, 18, 25], "paragraph": 26, "parallel": [10, 17, 25], "paramet": [7, 9], "parent": [2, 11], "pariatur": 7, "parser": [6, 7, 9, 11], "parsl": 9, "part": [1, 4, 7, 9, 12, 14, 15, 16, 18, 19, 21, 23, 24, 27], "particip": [4, 10], "particl": 9, "particle_mass": 9, "particular": [2, 11], "pass": [3, 19], "passeng": 15, "past": [7, 9, 18, 22, 27], "path": [22, 24, 27], "pattern": [21, 22], "pdf": 12, "peac": 15, "peaceiri": 7, "peer": 16, "peerj": 16, "pencil": 21, "peopl": [4, 9, 12, 18, 19, 21, 22, 23, 24, 25, 26], "perfect": 26, "perfetto": 15, "perform": [10, 15], "perhap": 2, "period": 15, "perl": 14, "permalink": 25, "permiss": [7, 14, 18, 24], "persist": [16, 27], "person": [2, 9, 22, 23, 26], "pg2600": 15, "phd": [2, 6, 9], "phone": 15, "pi": 17, "pick": 18, "pictur": [8, 17, 22], "pid": [16, 27], "piec": [4, 22], "pin": 12, "pip": [6, 11, 14], "pipelin": [6, 12], "pipenv": 6, "pixi": 6, "place": [4, 6, 7, 12, 17], "placehold": 27, "plai": 11, "plain": 25, "plan": 23, "planet": 5, "plaqu": 2, "platform": [12, 25], "pleas": [1, 3, 4, 9, 11, 15, 16, 21, 23, 25, 27], "plot": 8, "plot_result": 8, "plu": [1, 27], "plug": 18, "plugin": 18, "poetri": [6, 14], "point": [3, 4, 6, 7, 9, 14, 21, 22, 25, 27], "pointer": 2, "polici": 18, "popular": [4, 7, 9], "portabl": [6, 8, 9], "portion": [18, 23], "posit": 6, "possibl": [1, 3, 4, 6, 9, 10, 14, 16, 18, 21, 22, 24, 25, 26], "possibli": [3, 9, 24, 27], "post": [6, 7, 16, 19], "postdoc": 9, "postpon": 26, "potenti": [9, 15], "power": [1, 14, 27], "practic": [0, 4, 10, 12, 14, 16, 17, 18, 19, 20, 23, 24, 25], "pre": [9, 11, 21], "precis": [4, 6, 15], "predict": [8, 15, 19], "predictor": 10, "prefer": [10, 11, 17, 22], "prematur": 15, "present": [7, 18], "prevent": [16, 27], "preview": [7, 22], "previou": [1, 3, 4, 9, 21, 24, 25], "previous": [4, 7, 21], "primarili": 16, "principl": 16, "print": [2, 7, 11, 15, 17, 21], "prioriti": 9, "privaci": 9, "pro": [6, 11, 15, 17], "probabl": [3, 7, 11, 12, 15, 19, 27], "problem": [1, 3, 4, 9, 15, 18, 25], "process": [1, 9, 15, 19, 21], "processor": [11, 15], "procur": 16, "produc": 8, "product": 1, "profession": 6, "profil": [9, 10, 11], "program": [8, 9, 11, 15, 17, 18, 21, 23], "programm": 15, "progress": [4, 5, 6, 7], "proident": 7, "project": [2, 3, 4, 6, 7, 10, 11, 14, 15, 17, 18, 19, 20, 21, 23, 25], "prompt": 11, "prone": 9, "properti": 19, "propos": [3, 4, 24], "proprietari": [6, 18], "protect": [18, 26], "provid": [6, 7, 9, 11, 15, 16, 18, 22, 24, 26], "public": [2, 12, 14, 16, 18, 24, 27], "publicli": [16, 18, 27], "publish": [4, 6, 10, 12, 14, 18, 23], "publish_branch": 7, "publish_dir": 7, "pull": [2, 19, 23, 25, 26, 27], "pull_request": [7, 19], "purpos": [3, 7, 19, 21], "push": [1, 2, 7, 14, 19, 24, 27], "put": [4, 6, 14, 23], "py": [6, 7, 8, 12, 14, 15, 19, 22, 25, 27], "pyc": 26, "pycodestyl": 9, "pyenv": 6, "pyflak": 9, "pyinstrument": 15, "pylint": 9, "pympler": 15, "pypi": 10, "pypi_token": 14, "pyproject": 14, "pyre": 9, "pyright": 9, "pytest": [6, 11], "python": [5, 8, 9, 12, 15, 17, 19], "q": 10, "qualiti": 1, "quarto": 25, "question": [6, 14, 22, 25], "queue": 9, "qui": 7, "quick": [14, 21], "quickstart": 7, "quirk": 11, "quit": [1, 6, 18, 27], "quot": 15, "quota": 6, "r": [6, 7, 9, 15, 17, 18], "radiu": [8, 17], "rai": 15, "rais": 19, "ram": 6, "ran": 27, "random": 21, "rang": [1, 6, 9], "rare": 23, "rather": [7, 9, 15, 26], "raw": [6, 11], "re": [9, 11, 14, 15, 18], "read": [2, 3, 4, 12, 15, 17, 18], "readabl": 9, "readi": [4, 11, 24], "readm": [12, 21, 27], "readthedoc": [7, 9, 12, 13], "real": [12, 14, 15, 16, 19, 22, 27], "realiz": [6, 18], "realli": [18, 26, 27], "reason": [3, 18, 22, 26], "rebas": 23, "rebuild": 7, "receiv": [4, 9], "recent": [6, 12, 24], "recip": [3, 6, 12], "recipi": 9, "reciproc": 18, "recogn": [1, 4, 9, 16, 25], "recommend": [4, 6, 7, 9, 10, 11, 14, 15, 16, 17, 21, 22, 24], "record": [6, 15, 21], "recov": 26, "recoveri": 27, "recreat": 6, "red": 27, "redirect": [22, 27], "reduc": 23, "ref": [3, 7], "refactor": 10, "refer": [2, 3, 4, 7, 12, 16, 19, 24, 26, 27], "reflect": 18, "refresh": 7, "regard": 7, "regist": 4, "regress": 19, "reject": 3, "rel": 8, "relat": 6, "releas": [2, 7, 10, 14, 16, 18, 19, 21, 26], "relev": [2, 26], "relicens": 18, "reload": 27, "remain": 16, "rememb": [1, 6, 18, 22, 24, 25, 26, 27], "remot": [2, 3, 4, 27], "remov": [6, 9, 18, 23, 24, 27], "renam": 27, "render": [7, 12], "renv": 6, "repair": 26, "repeat": [4, 21], "replac": [3, 5, 6, 14, 21, 27], "repo": [10, 12, 18, 20], "report": [15, 19], "repositori": [0, 1, 5, 6, 7, 10, 12, 14, 16, 18, 21, 25, 26, 27], "reprehenderit": 7, "repres": [15, 18, 27], "reproduc": [5, 7, 11, 18, 25], "request": [2, 9, 18, 19, 23, 25, 26], "requir": [1, 3, 4, 6, 7, 8, 11, 12, 14, 15, 18, 21, 22], "rerun": 12, "research": [1, 4, 6, 11, 16, 18], "resid": 2, "resolut": [10, 20], "resolv": [4, 11], "resourc": 12, "respons": 4, "rest": 9, "restart": 27, "result": [1, 3, 8, 14, 15, 18, 19, 23], "retrac": 14, "return": [7, 9, 15, 17, 19, 24], "reus": [9, 17, 18, 27], "reusabl": [6, 8, 12, 16], "review": [3, 7, 10, 16, 19, 24, 25, 26], "reviv": 19, "rewrit": [16, 18, 19], "right": [3, 4, 7, 9, 19, 21, 22, 24, 27], "risk": [18, 23, 26], "rmd": 7, "room": 18, "root": 15, "row": 8, "rst": 7, "rstudio": [7, 27], "rtd": [6, 7, 11], "ruff": [6, 9, 11], "rule": [6, 9, 18], "run": [3, 6, 7, 9, 11, 12, 14, 15, 16, 19, 22, 27], "rust": 6, "rw": 27, "safe": [24, 26], "safeguard": 26, "sagemathcloud": 12, "sai": [4, 16, 21, 22, 24], "same": [0, 1, 2, 6, 7, 9, 10, 12, 15, 17, 18, 21, 22, 23, 24, 25, 26, 27], "sampl": 8, "sandbox": [12, 16], "satisfi": 18, "save": [7, 8, 9, 11, 15, 26], "saw": [21, 22], "scale": [8, 15], "scalen": [6, 11, 15], "scari": 19, "scatter_plot": 21, "schmitz": 18, "sci": 16, "scicomp": [6, 11, 15], "scienc": 11, "scientif": 11, "scipi": [6, 7, 11, 18, 19, 26], "score": 9, "scratch": 6, "screen": [9, 14, 22], "screenshot": [11, 24, 27], "scriberia": 16, "script": [6, 8, 9, 16, 19, 25], "scroll": [4, 24], "search": [7, 9, 25], "second": [6, 8, 11, 15, 26], "secret": [7, 14], "section": [3, 7, 9, 11, 12, 23], "secur": 9, "sed": 7, "see": [1, 3, 4, 6, 7, 9, 12, 15, 18, 19, 21, 22, 24, 27], "seed": 21, "seem": 21, "seen": [10, 18], "select": [1, 2, 7, 22, 24, 27], "selector": [21, 24], "self": [6, 11, 17, 18], "selfish": 23, "selv": [8, 12, 18], "send": [9, 24, 25, 27], "send_messag": 9, "sens": 21, "sensit": 21, "separ": [6, 11, 19, 21], "sequenc": 15, "server": [2, 7, 9], "servic": [3, 12, 15, 16, 18, 19, 27], "session": [11, 22], "set": [3, 4, 6, 8, 9, 10, 11, 14, 15, 19, 21, 27], "setup": [7, 14, 19, 22, 27], "setuptool": 14, "sever": [11, 21], "sh": [11, 19], "shape": 17, "share": [4, 5, 6, 9, 10, 14, 16, 18, 20, 21, 23, 25], "sharealik": 18, "shell": [6, 7, 11, 17, 19], "short": [3, 15, 21, 27], "shortcut": 22, "shortli": 22, "should": [3, 4, 6, 7, 9, 11, 16, 18, 21, 22, 24, 27], "shouldn": 24, "show": [8, 9, 12, 21, 22, 24], "side": [4, 17, 21], "sidebar": [21, 22, 24], "sign": [1, 21], "signal": 4, "signific": 21, "silli": 3, "similar": [6, 16, 18, 21, 22, 25, 27], "simpl": [8, 9, 14, 16, 17, 19, 22, 26, 27], "simpler": [3, 14], "simplifi": [17, 26], "simul": 15, "sinc": [4, 7, 9, 11, 22, 24, 25, 27], "singl": [1, 21], "singularityc": 6, "sint": 7, "sit": 7, "site": 7, "situat": [2, 6], "size": [8, 26], "skip": [4, 10, 24], "slatkin": 9, "slide": [12, 18], "slightli": 22, "slow": 15, "slower": 17, "slowest": 15, "small": [1, 6, 9, 15, 26, 27], "smaller": [2, 26], "smallest": 9, "smith": 16, "snakemak": [6, 9, 11], "snapshot": [2, 19, 21], "so": [1, 3, 4, 6, 9, 11, 14, 15, 18, 21, 22, 26, 27], "social": [16, 18], "softwar": [3, 6, 7, 15, 25], "solut": [1, 3, 6], "solv": 1, "solver": 6, "some": [1, 3, 4, 6, 7, 8, 9, 10, 12, 15, 18, 21, 22, 24, 25, 26, 27], "some_funct": 15, "somebodi": [1, 3, 4, 6, 17, 18, 19, 23, 25], "somefil": 17, "somehow": 24, "someon": [4, 18, 23, 26], "somestr": 17, "someth": [1, 3, 4, 7, 8, 11, 18, 19, 23, 24, 25, 26], "sometim": [6, 15, 23, 26], "somewhat": 22, "somewher": [18, 25], "soon": 2, "sooner": [6, 15], "sort": [9, 17], "sorted_scor": 9, "sound": 25, "sourc": [1, 3, 5, 6, 7, 9, 11, 21, 22, 24, 25, 27], "space": [8, 21], "spack": 6, "specif": [1, 19, 21], "specifi": [4, 6, 8, 9, 14], "spend": [15, 19], "spent": 15, "spheric": 17, "sphinx": [6, 11, 12], "sphinx_rtd_them": 7, "split": [9, 26], "spy": 15, "ssh": [10, 22, 27], "stabl": 8, "stackoverflow": 18, "stage": 24, "stai": 2, "standard": [6, 9, 15, 18, 22], "start": [3, 4, 6, 10, 14, 15, 21, 22, 23, 24, 25, 26, 27], "stash": 26, "state": [15, 17, 21], "stateless": 17, "static": [12, 18], "station": 15, "statist": 15, "statu": [21, 24], "step": [1, 3, 4, 7, 8, 10, 11, 15, 16, 19, 21, 22, 24, 26, 27], "stick": 21, "sticki": [21, 24], "still": [6, 9, 16, 19, 21, 24, 27], "stop": [15, 21], "storag": 6, "store": [15, 22], "str": [9, 15, 17], "strawberri": 9, "string_numb": 9, "strong": 18, "strongli": 1, "structur": [15, 17, 19], "struggl": 6, "student": [6, 9], "studi": [8, 10], "style": 26, "submit": [2, 3, 4], "submitt": 1, "subscript": 12, "success": [11, 26], "successfulli": 4, "suggest": [2, 3, 4, 18, 25], "suitabl": [4, 25], "summar": 26, "summari": 16, "sunt": 7, "super": 6, "supercomput": [2, 9], "superfund": 6, "support": [6, 9, 12, 16, 18, 21], "sure": [3, 4, 9, 15, 19, 21, 23, 24, 25, 26, 27], "surfac": 17, "surface_area": 17, "surgeri": 15, "surpris": 15, "svg": 8, "switch": [6, 9, 14, 24, 27], "symbol": [1, 22, 27], "symlink": 14, "sync": [3, 6], "syrupi": 19, "system": [6, 8, 11, 14, 25, 27], "systemat": 7, "t": [1, 4, 6, 7, 9, 10, 11, 15, 16, 18, 21, 22, 23, 24, 27], "t_k": 7, "tab": [1, 4, 7, 21, 22], "tag": [2, 26], "take": [2, 3, 6, 8, 12, 17, 22, 27], "target": 24, "task": [1, 3, 4, 5, 7, 9, 10, 19, 21, 22, 25], "teach": 9, "team": [2, 4], "technic": [1, 3, 4, 7, 23], "techniqu": [8, 19], "tell": [3, 19, 23], "temp_c": [7, 17, 19], "temp_f": [17, 19], "temp_k": 7, "temperatur": [7, 19], "templat": 26, "tempor": 7, "temporari": 21, "tend": 9, "term": [6, 18, 22], "termin": [10, 11, 21, 22, 27], "terminologi": 18, "test": [8, 9, 10, 13, 17, 21, 25], "test_": 19, "test_add": 19, "test_fahrenheit_to_celsiu": 19, "text": [7, 8, 10, 15, 23, 25], "than": [6, 9, 15, 22, 25, 26, 27], "thank": 4, "thei": [4, 6, 9, 11, 15, 17, 18, 21, 22, 24, 25, 26], "them": [1, 3, 4, 6, 10, 12, 15, 18, 19, 21, 24, 25], "theme": [6, 7, 11], "themselv": [2, 24], "therefor": 10, "thesi": 25, "thi": [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 21, 23, 24, 25, 26, 27], "thing": [4, 6, 9, 11, 14, 17, 21, 22, 23, 24, 26], "think": [4, 18, 23, 24], "those": [24, 27], "though": 11, "thought": 19, "thousand": 9, "thread": 27, "three": [7, 9, 13, 21, 22, 24, 27], "threshold": 7, "through": [1, 3, 4, 10, 11, 15, 16], "thu": [11, 24], "thumb": 7, "tile": 11, "time": [2, 3, 6, 7, 9, 11, 12, 15, 18, 19, 21, 22, 23, 24, 25, 27], "timelin": 22, "tini": [1, 6, 7], "titl": [4, 16, 24], "toctre": 7, "togeth": [1, 2, 8, 10, 11, 13, 14, 21, 22, 24, 26], "token": 14, "told": 17, "tolstoi": 15, "toml": [6, 9, 14], "tomorrow": 24, "too": [7, 9, 21, 22, 26], "took": 15, "tool": [7, 10, 12, 14, 22, 25, 27], "top": [4, 7, 8, 17, 21, 22, 24, 27], "topic": 11, "total": 27, "toward": [1, 19, 22, 24], "trace": 6, "tracemalloc": 15, "track": [4, 15, 21, 22, 26, 27], "tradition": 6, "train": [8, 15], "translat": 19, "travel": 6, "travers": [15, 19], "tri": [6, 25, 27], "trick": 21, "tricki": 18, "troubleshoot": [4, 27], "true": [7, 17, 19], "truli": 27, "trust": 22, "try": [3, 4, 6, 7, 8, 9, 10, 12, 15, 16, 19, 21, 22, 23, 24, 25, 27], "tune": 2, "tupl": [9, 17], "ture": 16, "turn": [10, 20, 21, 25, 26], "tutori": [7, 17, 21, 24], "twine": 14, "two": [1, 3, 7, 8, 10, 12, 18, 22, 23, 24, 25, 27], "txt": [6, 9, 11, 12, 14, 15], "type": [4, 10, 11, 14, 16, 21, 22, 24], "typic": [2, 9, 10, 18, 26], "typo": 1, "typographi": 7, "u": [4, 6, 8, 9, 15, 18, 21, 22, 24, 27], "ubuntu": [7, 14, 19], "ugli": 26, "ui": 6, "uit": 18, "ullamco": 7, "un": 24, "uncom": 14, "under": [2, 18, 22, 27], "understand": [1, 6, 8, 9, 15, 17, 18, 21, 22, 23, 24], "understood": [12, 26], "undo": 26, "unfinish": [26, 27], "unfortun": [11, 14, 22], "union": [14, 18], "uniq": 17, "uniqu": [2, 9, 15, 21, 22], "unique_word": [9, 15], "unit": 26, "univers": [6, 18, 25], "unix": 17, "unless": [6, 11, 18, 27], "unmaintain": 6, "unrel": [9, 23, 26], "unsur": [3, 11, 19, 21, 26], "untest": 26, "until": 4, "unus": 9, "unwatch": 4, "up": [3, 4, 6, 10, 11, 14, 15, 17, 23, 24, 26, 27], "updat": [1, 2, 11, 18, 26], "upload": [14, 27], "upper": 21, "uproar": 6, "upstream": [4, 22, 23, 24], "url": [3, 4, 12, 14, 21, 22, 27], "us": [1, 2, 3, 4, 5, 6, 7, 15, 16, 18, 19, 21, 24, 25, 27], "usabl": 11, "usag": [8, 14, 15, 21, 22], "user": [3, 4, 11, 18, 21, 22, 24, 27], "usernam": [3, 4, 12, 21, 22], "usual": 22, "ut": 7, "utf": [9, 15], "uv": 6, "v": [4, 10, 21, 24, 27], "v1": [7, 19, 21], "v4": [7, 14, 19], "v5": 14, "valid": 16, "valu": [4, 9, 16, 17], "vanilla": 9, "variabl": [9, 22], "variou": 18, "ve": 4, "velit": 7, "veniam": 7, "venu": 16, "venv": 6, "verbos": [3, 4, 9], "veri": [1, 3, 4, 7, 11, 14, 19, 21, 22, 26, 27], "verifi": [3, 4, 27], "versatil": 14, "version": [2, 7, 9, 10, 12, 14, 16, 19, 21, 22, 24, 27], "version1": 21, "version2": 21, "via": [4, 12, 21, 27], "video": 16, "view": [1, 3, 4, 7, 12, 15, 21, 22, 24], "violat": 9, "virtual": 6, "virtualenv": 6, "visibl": [4, 22], "visit": [7, 12], "visual": 12, "vl": [6, 7, 11, 19], "voil\u00e0": 12, "vol": 16, "volupt": 7, "vote": [19, 21], "w": [7, 9, 15], "wa": [3, 4, 6, 7, 9, 17, 21, 22, 25, 26], "wai": [1, 3, 4, 6, 7, 9, 11, 14, 16, 17, 19, 21, 22, 23, 24, 26], "walk": [3, 4], "want": [1, 3, 4, 7, 11, 14, 16, 18, 19, 21, 22, 24, 25, 27], "war": 15, "warm": 7, "warn": 9, "watch": [4, 23], "we": [1, 2, 3, 4, 6, 7, 9, 10, 12, 13, 14, 16, 18, 19, 21, 22, 23, 24, 26, 27], "web": [1, 4, 6, 7, 11, 16, 25, 27], "websit": [7, 18, 25, 27], "welcom": 10, "well": 17, "were": [4, 24], "what": [1, 3, 6, 8, 9, 11, 15, 16, 17, 21, 22, 23, 24, 27], "whatthecommit": 26, "wheel": 7, "when": [2, 3, 4, 6, 7, 9, 14, 15, 18, 19, 21, 23, 24, 25, 26], "where": [2, 3, 4, 6, 9, 10, 15, 16, 18, 21, 22, 23, 24, 25, 26], "wherea": 2, "wherev": 11, "whether": [2, 3, 8, 9, 15, 18, 19, 24], "which": [1, 2, 3, 4, 6, 7, 10, 11, 12, 14, 15, 16, 17, 18, 19, 21, 23, 24, 25, 26, 27], "while": 6, "who": [3, 4, 12, 18], "whole": [1, 2, 15, 19, 22], "why": [3, 6, 9, 14, 16, 26], "willing": 18, "window": [4, 11, 22], "wish": [3, 4, 11, 15, 17], "within": [0, 2, 10, 24], "without": [1, 3, 4, 6, 7, 9, 11, 15, 18, 22, 24, 25, 27], "wonder": 3, "word": [9, 15, 18, 19, 24, 25], "work": [1, 2, 3, 4, 7, 8, 9, 10, 12, 14, 16, 19, 21, 23, 24, 25, 27], "workflow": [3, 7, 9, 10, 14, 19], "workshop": [3, 4, 5, 10, 15, 19, 22, 24, 25], "world": [15, 19], "worst": 18, "worth": 15, "would": [2, 3, 14, 16, 17, 18, 19, 22, 24, 26], "wrap": 9, "write": [2, 3, 4, 6, 7, 12, 14, 18, 19, 21, 27], "written": 8, "wrong": [4, 9, 15], "wrote": 10, "www": 15, "x": [9, 15, 17], "x86_64": 11, "xkcd": 6, "xy": 17, "y": [9, 17], "yaml": [6, 9], "yapf": 9, "yappi": 15, "ye": [14, 22, 27], "year": [16, 18, 26], "yellow": 24, "yet": [4, 19, 21, 24, 27], "yml": [6, 7, 11, 12, 14, 19], "you": [1, 2, 3, 4, 6, 10, 12, 15, 17, 18, 19, 23, 24, 25, 26, 27], "your": [1, 2, 4, 6, 8, 10, 14, 15, 18, 19, 20, 21, 22, 23, 26], "yourself": [18, 27], "z": 9, "zenodo": [12, 16, 18, 27], "zip": 25, "zola": 7, "zombi": 7}, "titles": ["Collaborative version control and code review", "Practicing code review", "Concepts around collaboration", "How to contribute changes to repositories that belong to others", "Collaborating within the same repository", "Credit", "Reproducible environments and dependencies", "Where to start with documentation", "Example project: 2D classification task using a nearest-neighbor predictor", "Tools and useful practices", "Reproducible research software development using Python (ML example)", "Software install instructions", "Sharing notebooks", "Other useful tooling for notebooks", "Creating a Python package and deploying it to PyPI", "Profiling", "How to publish your code", "Concepts in refactoring and modular code design", "Choosing a software license", "Automated testing", "Introduction to version control with Git and GitHub", "Creating branches and commits", "Forking, cloning, and browsing", "Conflict resolution", "Merging changes and contributing to the project", "Motivation", "Practical advice: How much Git is necessary?", "Optional: How to turn your project to a Git repo and share it"], "titleterms": {"": 3, "1": [4, 14, 21, 22, 24], "15": 12, "2": [4, 14, 21, 22, 24], "20": [21, 22, 27], "25": [1, 3, 4], "2d": 8, "3": [4, 14, 21, 22, 24], "4": [4, 21, 22, 24], "5": [4, 21, 22, 24], "6": [4, 21, 22, 24], "7": [4, 21, 24], "8": [9, 21], "And": 4, "But": 17, "If": 11, "In": 7, "It": 6, "One": 15, "The": [6, 23], "There": 11, "about": [7, 25, 26], "action": [7, 14, 19], "activ": 11, "ad": [4, 19], "addit": 15, "address": 1, "advic": 26, "again": 21, "ai": 9, "all": [9, 26], "allow": [7, 22], "an": [4, 9, 11, 14, 22], "anaconda": 11, "analogi": 15, "annot": 9, "anyon": 12, "api": 7, "approach": 3, "ar": [9, 11, 17, 22], "arbitrari": 21, "around": 2, "ask": 1, "assist": 9, "auto": [7, 9], "autom": [3, 19], "avoid": [6, 9, 23, 26], "back": 25, "background": 21, "basic": 22, "befor": 17, "begin": 24, "belong": 3, "best": 6, "between": 2, "beyond": 9, "big": 10, "binder": 12, "box": 9, "branch": [2, 4, 21, 24, 25, 26], "brows": [21, 22], "build": 7, "can": [3, 9, 22], "catch": 9, "chang": [1, 2, 3, 4, 24], "check": 9, "checklist": 7, "choic": 11, "choos": [11, 15, 18], "citabl": 16, "cite": 16, "class": 17, "classif": 8, "clone": [2, 22], "code": [0, 1, 7, 9, 13, 15, 16, 17, 22, 25], "collabor": [0, 2, 3, 4, 17, 25], "collect": 9, "command": [22, 26], "comment": 1, "commit": [2, 21, 22, 26], "commun": 6, "compar": [21, 22], "complex": 26, "comprehens": 9, "compromis": 11, "concept": [2, 17], "conda": [6, 11], "configur": 9, "conflict": 23, "confus": 7, "consid": 9, "constitut": 18, "contain": 6, "contribut": [3, 24], "control": [0, 20], "copi": 22, "copyright": 18, "coupl": 3, "creat": [4, 14, 21, 22, 24], "credit": 5, "cross": 4, "current": 16, "databas": 17, "debug": 9, "delet": 24, "demo": [7, 12, 14, 16, 18], "demonstr": [23, 25], "depend": [6, 12], "deploi": 14, "deriv": 18, "design": 17, "develop": 10, "differ": 12, "digit": 12, "discuss": [1, 3, 8, 15, 16, 18, 25], "disk": 17, "distribut": 6, "di\u00e1taxi": 7, "do": 25, "docstr": 7, "document": [7, 12, 17], "doi": 12, "don": 3, "draft": 4, "dynam": 12, "each": 26, "ecosystem": 6, "els": 19, "end": 19, "enough": 7, "enumer": 9, "environ": [6, 11], "ergonom": 9, "etc": 11, "even": 15, "exampl": [8, 10], "exercis": [1, 3, 4, 7, 12, 15, 16, 18, 19, 21, 22, 24, 27], "exist": [15, 22], "explain": [6, 16], "featur": 25, "file": [9, 21, 22], "fill": 24, "filter": 9, "flit": 14, "focu": [8, 16], "follow": 9, "fork": [2, 3, 22], "format": 13, "formatt": 9, "from": [3, 14, 17], "function": [9, 17], "futur": 7, "gener": 7, "get": 12, "git": [20, 26, 27], "github": [4, 7, 14, 16, 19, 20, 22, 27], "gitlab": [16, 27], "goal": [8, 10], "good": 11, "graph": 22, "great": 16, "guess": 15, "guid": 9, "have": [3, 11, 14], "help": [1, 3, 14], "here": 9, "hint": [4, 9], "histori": 22, "host": 7, "how": [1, 3, 4, 6, 7, 11, 12, 16, 17, 19, 22, 26, 27], "human": 23, "i": [3, 4, 7, 15, 16, 17, 19, 26, 27], "idea": 3, "identifi": 12, "imagin": 15, "includ": 22, "index": [6, 9], "indic": 9, "initi": 14, "insert": 15, "instal": [6, 11, 14], "instantan": 22, "instead": [9, 15], "instruct": 11, "instructor": [7, 8, 13, 19, 22], "introduct": 20, "isol": 6, "issu": 4, "iter": 9, "jupyterlab": 11, "just": [14, 21], "keep": [11, 25], "keypoint": [6, 7], "know": 9, "languag": 7, "larg": 26, "last": 22, "learn": 8, "level": 26, "licens": 18, "like": [9, 25], "line": [22, 26], "lint": 9, "linter": 9, "list": 9, "local": [3, 14], "london": 15, "loop": 9, "machin": 6, "made": 21, "main": [4, 21], "make": [4, 9, 12, 16], "manag": 6, "mani": [7, 11, 15], "map": 9, "markdown": 7, "matter": 18, "measur": 15, "merg": [24, 25], "messag": 26, "metadata": 14, "method": 11, "min": [1, 3, 4, 12, 21, 22, 27], "miniforg": 11, "ml": 10, "modif": 22, "modifi": [1, 4, 21, 22], "modular": 17, "mondai": 10, "more": [7, 9, 14, 16, 18], "motiv": [3, 19, 25], "much": 26, "my": [3, 6], "myst": 7, "navig": 24, "nearest": 8, "necessari": 26, "need": [7, 9, 25], "neighbor": 8, "network": [17, 22], "new": [4, 21], "note": [7, 8, 13, 19, 22], "notebook": [12, 13], "o": [9, 17], "object": [2, 6, 7, 9, 12, 14, 15, 16, 18, 19, 21, 22, 24, 25, 27], "often": [7, 9, 19], "one": 14, "open": [3, 4, 9], "optim": 15, "option": [7, 27], "origin": 24, "other": [3, 13], "our": 9, "out": 24, "over": 9, "packag": [6, 14], "paper": 16, "parallel": 9, "part": 6, "particular": 22, "path": 9, "pathlib": 9, "peopl": 3, "pep": 9, "permiss": 3, "pictur": 10, "pin": 6, "possibl": [7, 19], "practic": [1, 6, 9, 15, 21, 26], "predictor": 8, "prefer": 9, "prepar": [1, 3, 4, 7, 10, 22], "print": 9, "process": 24, "profil": 15, "project": [8, 9, 22, 24, 26, 27], "protect": 4, "public": 6, "publish": [16, 27], "pull": [1, 3, 4, 24], "pure": 17, "push": [3, 4], "put": [16, 27], "pypi": [6, 14], "pytest": 19, "python": [6, 7, 10, 11, 14], "question": [9, 17], "read": [7, 9], "readm": 7, "recip": 4, "recoveri": 3, "refactor": 17, "referenc": 4, "rel": 9, "remix": 18, "remov": 11, "renam": 21, "repo": 27, "report": 6, "repositori": [2, 3, 4, 22, 24], "reproduc": [6, 10, 12], "request": [1, 3, 4, 24], "research": 10, "resolut": 23, "resolv": 23, "resourc": [15, 16, 18], "restructuredtext": 7, "review": [0, 1, 4], "right": 15, "roll": 25, "run": 8, "same": [3, 4], "sampl": [15, 18], "schedul": 10, "scientif": 16, "scope": 9, "script": 14, "search": 22, "self": 7, "set": 7, "share": [12, 22, 27], "should": [15, 26], "side": 23, "simplest": 15, "singl": 22, "situat": 18, "size": 15, "slice": 9, "snapshot": 25, "softwar": [10, 11, 16, 18, 27], "solut": [4, 15, 18, 21, 22, 24, 25], "sphinx": 7, "stage": 26, "start": [7, 11, 17, 19], "statement": 9, "static": 9, "statu": 26, "step": 14, "structur": 9, "style": 9, "subprocess": 9, "subwai": 15, "suggest": [1, 11], "summari": [1, 3, 4, 21, 22, 24], "supercomput": 6, "switch": 21, "synchron": 2, "system": [9, 15], "t": 3, "tag": 21, "talk": 25, "task": 8, "taught": 19, "taxonomi": 18, "test": [3, 14, 19], "than": [7, 14], "thesi": 6, "thi": [14, 22], "through": [21, 22, 24], "thursdai": 10, "time": 26, "timer": 15, "tool": [6, 9, 11, 13, 15], "toward": [3, 4], "trace": 15, "track": 25, "train": 22, "try": [11, 14], "tuesdai": 10, "turn": 27, "two": [6, 9, 21], "type": 9, "typic": 25, "underground": 15, "unit": 19, "unpack": 9, "up": [7, 9], "updat": [3, 6], "upstream": 3, "us": [8, 9, 10, 11, 12, 13, 14, 22, 26], "v": [7, 15, 22], "verifi": [11, 24], "version": [0, 6, 20, 25], "via": 11, "wai": 12, "walk": [21, 22, 24], "want": 15, "we": [8, 11, 15, 25], "wednesdai": 10, "what": [4, 7, 14, 18, 19, 25, 26], "when": 22, "where": [7, 19], "which": [9, 22], "who": 22, "why": [7, 18, 25], "within": [3, 4], "word": 22, "work": [6, 11, 18, 22, 26], "write": [9, 17, 26], "you": [7, 9, 11, 14, 16, 21, 22], "your": [3, 7, 9, 11, 12, 16, 17, 24, 27], "yourself": 22, "zip": 9}})
\ No newline at end of file
diff --git a/branch/main/singlehtml/_images/8-fair-principles.jpg b/branch/main/singlehtml/_images/8-fair-principles.jpg
new file mode 100644
index 0000000..efdfddb
Binary files /dev/null and b/branch/main/singlehtml/_images/8-fair-principles.jpg differ
diff --git a/branch/main/singlehtml/_images/add-suggestion.png b/branch/main/singlehtml/_images/add-suggestion.png
new file mode 100644
index 0000000..0a28544
Binary files /dev/null and b/branch/main/singlehtml/_images/add-suggestion.png differ
diff --git a/branch/main/singlehtml/_images/all-checks-failed.png b/branch/main/singlehtml/_images/all-checks-failed.png
new file mode 100644
index 0000000..5175684
Binary files /dev/null and b/branch/main/singlehtml/_images/all-checks-failed.png differ
diff --git a/branch/main/singlehtml/_images/bare-repository.png b/branch/main/singlehtml/_images/bare-repository.png
new file mode 100644
index 0000000..45286f9
Binary files /dev/null and b/branch/main/singlehtml/_images/bare-repository.png differ
diff --git a/branch/main/singlehtml/_images/binder.jpg b/branch/main/singlehtml/_images/binder.jpg
new file mode 100644
index 0000000..d04c467
Binary files /dev/null and b/branch/main/singlehtml/_images/binder.jpg differ
diff --git a/branch/main/singlehtml/_images/branches.png b/branch/main/singlehtml/_images/branches.png
new file mode 100644
index 0000000..2464b1c
Binary files /dev/null and b/branch/main/singlehtml/_images/branches.png differ
diff --git a/branch/main/singlehtml/_images/button.jpg b/branch/main/singlehtml/_images/button.jpg
new file mode 100644
index 0000000..4a0d99e
Binary files /dev/null and b/branch/main/singlehtml/_images/button.jpg differ
diff --git a/branch/main/singlehtml/_images/chart.svg b/branch/main/singlehtml/_images/chart.svg
new file mode 100644
index 0000000..23cb3e9
--- /dev/null
+++ b/branch/main/singlehtml/_images/chart.svg
@@ -0,0 +1 @@
+label 0 1 −12 −10 −8 −6 −4 −2 0 2 4 6 8 10 12 x −12 −10 −8 −6 −4 −2 0 2 4 6 8 10 12 x false true correct Are predictions correct? (accuracy: 0.94) label 0 1 −12 −10 −8 −6 −4 −2 0 2 4 6 8 10 12 x −12 −10 −8 −6 −4 −2 0 2 4 6 8 10 12 x 0 1 label Training data
\ No newline at end of file
diff --git a/branch/main/singlehtml/_images/chatgpt.png b/branch/main/singlehtml/_images/chatgpt.png
new file mode 100644
index 0000000..6794801
Binary files /dev/null and b/branch/main/singlehtml/_images/chatgpt.png differ
diff --git a/branch/main/singlehtml/_images/check-details.png b/branch/main/singlehtml/_images/check-details.png
new file mode 100644
index 0000000..456a247
Binary files /dev/null and b/branch/main/singlehtml/_images/check-details.png differ
diff --git a/branch/main/singlehtml/_images/clone-address.png b/branch/main/singlehtml/_images/clone-address.png
new file mode 100644
index 0000000..abdbd85
Binary files /dev/null and b/branch/main/singlehtml/_images/clone-address.png differ
diff --git a/branch/main/singlehtml/_images/clone-of-fork.png b/branch/main/singlehtml/_images/clone-of-fork.png
new file mode 100644
index 0000000..e610a1a
Binary files /dev/null and b/branch/main/singlehtml/_images/clone-of-fork.png differ
diff --git a/branch/main/singlehtml/_images/clone.png b/branch/main/singlehtml/_images/clone.png
new file mode 100644
index 0000000..9e79c46
Binary files /dev/null and b/branch/main/singlehtml/_images/clone.png differ
diff --git a/branch/main/singlehtml/_images/code-completion.gif b/branch/main/singlehtml/_images/code-completion.gif
new file mode 100644
index 0000000..e0bed42
Binary files /dev/null and b/branch/main/singlehtml/_images/code-completion.gif differ
diff --git a/branch/main/singlehtml/_images/comment.png b/branch/main/singlehtml/_images/comment.png
new file mode 100644
index 0000000..6ae948c
Binary files /dev/null and b/branch/main/singlehtml/_images/comment.png differ
diff --git a/branch/main/singlehtml/_images/commit-suggestion.png b/branch/main/singlehtml/_images/commit-suggestion.png
new file mode 100644
index 0000000..4ec4bb6
Binary files /dev/null and b/branch/main/singlehtml/_images/commit-suggestion.png differ
diff --git a/branch/main/singlehtml/_images/create-repository-with-readme.png b/branch/main/singlehtml/_images/create-repository-with-readme.png
new file mode 100644
index 0000000..2411834
Binary files /dev/null and b/branch/main/singlehtml/_images/create-repository-with-readme.png differ
diff --git a/branch/main/singlehtml/_images/create-repository.png b/branch/main/singlehtml/_images/create-repository.png
new file mode 100644
index 0000000..80fb99c
Binary files /dev/null and b/branch/main/singlehtml/_images/create-repository.png differ
diff --git a/branch/main/singlehtml/_images/draft-pr-wip.png b/branch/main/singlehtml/_images/draft-pr-wip.png
new file mode 100644
index 0000000..12181d5
Binary files /dev/null and b/branch/main/singlehtml/_images/draft-pr-wip.png differ
diff --git a/branch/main/singlehtml/_images/draft-pr.png b/branch/main/singlehtml/_images/draft-pr.png
new file mode 100644
index 0000000..2099349
Binary files /dev/null and b/branch/main/singlehtml/_images/draft-pr.png differ
diff --git a/branch/main/singlehtml/_images/exercise.png b/branch/main/singlehtml/_images/exercise.png
new file mode 100644
index 0000000..ddfe435
Binary files /dev/null and b/branch/main/singlehtml/_images/exercise.png differ
diff --git a/branch/main/singlehtml/_images/files-changed.png b/branch/main/singlehtml/_images/files-changed.png
new file mode 100644
index 0000000..7b58a22
Binary files /dev/null and b/branch/main/singlehtml/_images/files-changed.png differ
diff --git a/branch/main/singlehtml/_images/fork-after-update.png b/branch/main/singlehtml/_images/fork-after-update.png
new file mode 100644
index 0000000..38fc8c6
Binary files /dev/null and b/branch/main/singlehtml/_images/fork-after-update.png differ
diff --git a/branch/main/singlehtml/_images/fork.png b/branch/main/singlehtml/_images/fork.png
new file mode 100644
index 0000000..50cd9cc
Binary files /dev/null and b/branch/main/singlehtml/_images/fork.png differ
diff --git a/branch/main/singlehtml/_images/forkandclone.png b/branch/main/singlehtml/_images/forkandclone.png
new file mode 100644
index 0000000..d050183
Binary files /dev/null and b/branch/main/singlehtml/_images/forkandclone.png differ
diff --git a/branch/main/singlehtml/_images/github-branches.png b/branch/main/singlehtml/_images/github-branches.png
new file mode 100644
index 0000000..111eca1
Binary files /dev/null and b/branch/main/singlehtml/_images/github-branches.png differ
diff --git a/branch/main/singlehtml/_images/github-compare-and-pr.png b/branch/main/singlehtml/_images/github-compare-and-pr.png
new file mode 100644
index 0000000..efca375
Binary files /dev/null and b/branch/main/singlehtml/_images/github-compare-and-pr.png differ
diff --git a/branch/main/singlehtml/_images/github-comparing-changes.png b/branch/main/singlehtml/_images/github-comparing-changes.png
new file mode 100644
index 0000000..3f6f718
Binary files /dev/null and b/branch/main/singlehtml/_images/github-comparing-changes.png differ
diff --git a/branch/main/singlehtml/_images/github-contribute.png b/branch/main/singlehtml/_images/github-contribute.png
new file mode 100644
index 0000000..ef80527
Binary files /dev/null and b/branch/main/singlehtml/_images/github-contribute.png differ
diff --git a/branch/main/singlehtml/_images/github-merged.png b/branch/main/singlehtml/_images/github-merged.png
new file mode 100644
index 0000000..fcd4404
Binary files /dev/null and b/branch/main/singlehtml/_images/github-merged.png differ
diff --git a/branch/main/singlehtml/_images/github-navigate-to-branch.png b/branch/main/singlehtml/_images/github-navigate-to-branch.png
new file mode 100644
index 0000000..9ac4731
Binary files /dev/null and b/branch/main/singlehtml/_images/github-navigate-to-branch.png differ
diff --git a/branch/main/singlehtml/_images/good-vs-bad.svg b/branch/main/singlehtml/_images/good-vs-bad.svg
new file mode 100644
index 0000000..e787bea
--- /dev/null
+++ b/branch/main/singlehtml/_images/good-vs-bad.svg
@@ -0,0 +1,191 @@
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+ impure
+ bad code
+
+
+ impure
+ better code
+ pure
+
+
+ impure
+ good code
+ pure
+
+
+
diff --git a/branch/main/singlehtml/_images/gophers.png b/branch/main/singlehtml/_images/gophers.png
new file mode 100644
index 0000000..741406b
Binary files /dev/null and b/branch/main/singlehtml/_images/gophers.png differ
diff --git a/branch/main/singlehtml/_images/history.png b/branch/main/singlehtml/_images/history.png
new file mode 100644
index 0000000..faecc66
Binary files /dev/null and b/branch/main/singlehtml/_images/history.png differ
diff --git a/branch/main/singlehtml/_images/issue-number.png b/branch/main/singlehtml/_images/issue-number.png
new file mode 100644
index 0000000..be273a2
Binary files /dev/null and b/branch/main/singlehtml/_images/issue-number.png differ
diff --git a/branch/main/singlehtml/_images/leave-comment.png b/branch/main/singlehtml/_images/leave-comment.png
new file mode 100644
index 0000000..95e33c2
Binary files /dev/null and b/branch/main/singlehtml/_images/leave-comment.png differ
diff --git a/branch/main/singlehtml/_images/license-models.png b/branch/main/singlehtml/_images/license-models.png
new file mode 100644
index 0000000..f413617
Binary files /dev/null and b/branch/main/singlehtml/_images/license-models.png differ
diff --git a/branch/main/singlehtml/_images/license.png b/branch/main/singlehtml/_images/license.png
new file mode 100644
index 0000000..ec5cf85
Binary files /dev/null and b/branch/main/singlehtml/_images/license.png differ
diff --git a/branch/main/singlehtml/_images/merging.png b/branch/main/singlehtml/_images/merging.png
new file mode 100644
index 0000000..b954afb
Binary files /dev/null and b/branch/main/singlehtml/_images/merging.png differ
diff --git a/branch/main/singlehtml/_images/network.png b/branch/main/singlehtml/_images/network.png
new file mode 100644
index 0000000..96b97ae
Binary files /dev/null and b/branch/main/singlehtml/_images/network.png differ
diff --git a/branch/main/singlehtml/_images/new-repository.png b/branch/main/singlehtml/_images/new-repository.png
new file mode 100644
index 0000000..aff7677
Binary files /dev/null and b/branch/main/singlehtml/_images/new-repository.png differ
diff --git a/branch/main/singlehtml/_images/owl.png b/branch/main/singlehtml/_images/owl.png
new file mode 100644
index 0000000..7f90e9f
Binary files /dev/null and b/branch/main/singlehtml/_images/owl.png differ
diff --git a/branch/main/singlehtml/_images/packages.jpg b/branch/main/singlehtml/_images/packages.jpg
new file mode 100644
index 0000000..421b4fc
Binary files /dev/null and b/branch/main/singlehtml/_images/packages.jpg differ
diff --git a/branch/main/singlehtml/_images/pull-request-form.png b/branch/main/singlehtml/_images/pull-request-form.png
new file mode 100644
index 0000000..c6c6af8
Binary files /dev/null and b/branch/main/singlehtml/_images/pull-request-form.png differ
diff --git a/branch/main/singlehtml/_images/record-player.png b/branch/main/singlehtml/_images/record-player.png
new file mode 100644
index 0000000..467c838
Binary files /dev/null and b/branch/main/singlehtml/_images/record-player.png differ
diff --git a/branch/main/singlehtml/_images/sharing.png b/branch/main/singlehtml/_images/sharing.png
new file mode 100644
index 0000000..8368d6b
Binary files /dev/null and b/branch/main/singlehtml/_images/sharing.png differ
diff --git a/branch/main/singlehtml/_images/sync-fork.png b/branch/main/singlehtml/_images/sync-fork.png
new file mode 100644
index 0000000..848a054
Binary files /dev/null and b/branch/main/singlehtml/_images/sync-fork.png differ
diff --git a/branch/main/singlehtml/_images/testing-jupyter1.png b/branch/main/singlehtml/_images/testing-jupyter1.png
new file mode 100644
index 0000000..38bd6cc
Binary files /dev/null and b/branch/main/singlehtml/_images/testing-jupyter1.png differ
diff --git a/branch/main/singlehtml/_images/testing-jupyter2.png b/branch/main/singlehtml/_images/testing-jupyter2.png
new file mode 100644
index 0000000..53c541e
Binary files /dev/null and b/branch/main/singlehtml/_images/testing-jupyter2.png differ
diff --git a/branch/main/singlehtml/_images/testing-jupyter3.png b/branch/main/singlehtml/_images/testing-jupyter3.png
new file mode 100644
index 0000000..1b1e47a
Binary files /dev/null and b/branch/main/singlehtml/_images/testing-jupyter3.png differ
diff --git a/branch/main/singlehtml/_images/testing-jupyter4.png b/branch/main/singlehtml/_images/testing-jupyter4.png
new file mode 100644
index 0000000..25c7795
Binary files /dev/null and b/branch/main/singlehtml/_images/testing-jupyter4.png differ
diff --git a/branch/main/singlehtml/_images/testing-jupyter5.png b/branch/main/singlehtml/_images/testing-jupyter5.png
new file mode 100644
index 0000000..c8d3491
Binary files /dev/null and b/branch/main/singlehtml/_images/testing-jupyter5.png differ
diff --git a/branch/main/singlehtml/_images/turntable.png b/branch/main/singlehtml/_images/turntable.png
new file mode 100644
index 0000000..38829e7
Binary files /dev/null and b/branch/main/singlehtml/_images/turntable.png differ
diff --git a/branch/main/singlehtml/_images/unwatch.png b/branch/main/singlehtml/_images/unwatch.png
new file mode 100644
index 0000000..020a6c9
Binary files /dev/null and b/branch/main/singlehtml/_images/unwatch.png differ
diff --git a/branch/main/singlehtml/_images/upload-files.png b/branch/main/singlehtml/_images/upload-files.png
new file mode 100644
index 0000000..fd3ef8c
Binary files /dev/null and b/branch/main/singlehtml/_images/upload-files.png differ
diff --git a/branch/main/singlehtml/_images/vscode-authorize.png b/branch/main/singlehtml/_images/vscode-authorize.png
new file mode 100644
index 0000000..4d45dc2
Binary files /dev/null and b/branch/main/singlehtml/_images/vscode-authorize.png differ
diff --git a/branch/main/singlehtml/_images/vscode-publish-branch.png b/branch/main/singlehtml/_images/vscode-publish-branch.png
new file mode 100644
index 0000000..323ffb2
Binary files /dev/null and b/branch/main/singlehtml/_images/vscode-publish-branch.png differ
diff --git a/branch/main/singlehtml/_images/vscode-publish-to-github1.png b/branch/main/singlehtml/_images/vscode-publish-to-github1.png
new file mode 100644
index 0000000..883e756
Binary files /dev/null and b/branch/main/singlehtml/_images/vscode-publish-to-github1.png differ
diff --git a/branch/main/singlehtml/_images/vscode-publish-to-github2.png b/branch/main/singlehtml/_images/vscode-publish-to-github2.png
new file mode 100644
index 0000000..d0055db
Binary files /dev/null and b/branch/main/singlehtml/_images/vscode-publish-to-github2.png differ
diff --git a/branch/main/singlehtml/_images/vscode-publish-to-github3.png b/branch/main/singlehtml/_images/vscode-publish-to-github3.png
new file mode 100644
index 0000000..0f68d0e
Binary files /dev/null and b/branch/main/singlehtml/_images/vscode-publish-to-github3.png differ
diff --git a/branch/main/singlehtml/_images/vscode-start.png b/branch/main/singlehtml/_images/vscode-start.png
new file mode 100644
index 0000000..e783080
Binary files /dev/null and b/branch/main/singlehtml/_images/vscode-start.png differ
diff --git a/branch/main/singlehtml/_static/_sphinx_javascript_frameworks_compat.js b/branch/main/singlehtml/_static/_sphinx_javascript_frameworks_compat.js
new file mode 100644
index 0000000..8141580
--- /dev/null
+++ b/branch/main/singlehtml/_static/_sphinx_javascript_frameworks_compat.js
@@ -0,0 +1,123 @@
+/* Compatability shim for jQuery and underscores.js.
+ *
+ * Copyright Sphinx contributors
+ * Released under the two clause BSD licence
+ */
+
+/**
+ * small helper function to urldecode strings
+ *
+ * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL
+ */
+jQuery.urldecode = function(x) {
+ if (!x) {
+ return x
+ }
+ return decodeURIComponent(x.replace(/\+/g, ' '));
+};
+
+/**
+ * small helper function to urlencode strings
+ */
+jQuery.urlencode = encodeURIComponent;
+
+/**
+ * This function returns the parsed url parameters of the
+ * current request. Multiple values per key are supported,
+ * it will always return arrays of strings for the value parts.
+ */
+jQuery.getQueryParameters = function(s) {
+ if (typeof s === 'undefined')
+ s = document.location.search;
+ var parts = s.substr(s.indexOf('?') + 1).split('&');
+ var result = {};
+ for (var i = 0; i < parts.length; i++) {
+ var tmp = parts[i].split('=', 2);
+ var key = jQuery.urldecode(tmp[0]);
+ var value = jQuery.urldecode(tmp[1]);
+ if (key in result)
+ result[key].push(value);
+ else
+ result[key] = [value];
+ }
+ return result;
+};
+
+/**
+ * highlight a given string on a jquery object by wrapping it in
+ * span elements with the given class name.
+ */
+jQuery.fn.highlightText = function(text, className) {
+ function highlight(node, addItems) {
+ if (node.nodeType === 3) {
+ var val = node.nodeValue;
+ var pos = val.toLowerCase().indexOf(text);
+ if (pos >= 0 &&
+ !jQuery(node.parentNode).hasClass(className) &&
+ !jQuery(node.parentNode).hasClass("nohighlight")) {
+ var span;
+ var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg");
+ if (isInSVG) {
+ span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
+ } else {
+ span = document.createElement("span");
+ span.className = className;
+ }
+ span.appendChild(document.createTextNode(val.substr(pos, text.length)));
+ node.parentNode.insertBefore(span, node.parentNode.insertBefore(
+ document.createTextNode(val.substr(pos + text.length)),
+ node.nextSibling));
+ node.nodeValue = val.substr(0, pos);
+ if (isInSVG) {
+ var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
+ var bbox = node.parentElement.getBBox();
+ rect.x.baseVal.value = bbox.x;
+ rect.y.baseVal.value = bbox.y;
+ rect.width.baseVal.value = bbox.width;
+ rect.height.baseVal.value = bbox.height;
+ rect.setAttribute('class', className);
+ addItems.push({
+ "parent": node.parentNode,
+ "target": rect});
+ }
+ }
+ }
+ else if (!jQuery(node).is("button, select, textarea")) {
+ jQuery.each(node.childNodes, function() {
+ highlight(this, addItems);
+ });
+ }
+ }
+ var addItems = [];
+ var result = this.each(function() {
+ highlight(this, addItems);
+ });
+ for (var i = 0; i < addItems.length; ++i) {
+ jQuery(addItems[i].parent).before(addItems[i].target);
+ }
+ return result;
+};
+
+/*
+ * backward compatibility for jQuery.browser
+ * This will be supported until firefox bug is fixed.
+ */
+if (!jQuery.browser) {
+ jQuery.uaMatch = function(ua) {
+ ua = ua.toLowerCase();
+
+ var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
+ /(webkit)[ \/]([\w.]+)/.exec(ua) ||
+ /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
+ /(msie) ([\w.]+)/.exec(ua) ||
+ ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
+ [];
+
+ return {
+ browser: match[ 1 ] || "",
+ version: match[ 2 ] || "0"
+ };
+ };
+ jQuery.browser = {};
+ jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true;
+}
diff --git a/branch/main/singlehtml/_static/basic.css b/branch/main/singlehtml/_static/basic.css
new file mode 100644
index 0000000..f316efc
--- /dev/null
+++ b/branch/main/singlehtml/_static/basic.css
@@ -0,0 +1,925 @@
+/*
+ * basic.css
+ * ~~~~~~~~~
+ *
+ * Sphinx stylesheet -- basic theme.
+ *
+ * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+/* -- main layout ----------------------------------------------------------- */
+
+div.clearer {
+ clear: both;
+}
+
+div.section::after {
+ display: block;
+ content: '';
+ clear: left;
+}
+
+/* -- relbar ---------------------------------------------------------------- */
+
+div.related {
+ width: 100%;
+ font-size: 90%;
+}
+
+div.related h3 {
+ display: none;
+}
+
+div.related ul {
+ margin: 0;
+ padding: 0 0 0 10px;
+ list-style: none;
+}
+
+div.related li {
+ display: inline;
+}
+
+div.related li.right {
+ float: right;
+ margin-right: 5px;
+}
+
+/* -- sidebar --------------------------------------------------------------- */
+
+div.sphinxsidebarwrapper {
+ padding: 10px 5px 0 10px;
+}
+
+div.sphinxsidebar {
+ float: left;
+ width: 230px;
+ margin-left: -100%;
+ font-size: 90%;
+ word-wrap: break-word;
+ overflow-wrap : break-word;
+}
+
+div.sphinxsidebar ul {
+ list-style: none;
+}
+
+div.sphinxsidebar ul ul,
+div.sphinxsidebar ul.want-points {
+ margin-left: 20px;
+ list-style: square;
+}
+
+div.sphinxsidebar ul ul {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+div.sphinxsidebar form {
+ margin-top: 10px;
+}
+
+div.sphinxsidebar input {
+ border: 1px solid #98dbcc;
+ font-family: sans-serif;
+ font-size: 1em;
+}
+
+div.sphinxsidebar #searchbox form.search {
+ overflow: hidden;
+}
+
+div.sphinxsidebar #searchbox input[type="text"] {
+ float: left;
+ width: 80%;
+ padding: 0.25em;
+ box-sizing: border-box;
+}
+
+div.sphinxsidebar #searchbox input[type="submit"] {
+ float: left;
+ width: 20%;
+ border-left: none;
+ padding: 0.25em;
+ box-sizing: border-box;
+}
+
+
+img {
+ border: 0;
+ max-width: 100%;
+}
+
+/* -- search page ----------------------------------------------------------- */
+
+ul.search {
+ margin: 10px 0 0 20px;
+ padding: 0;
+}
+
+ul.search li {
+ padding: 5px 0 5px 20px;
+ background-image: url(file.png);
+ background-repeat: no-repeat;
+ background-position: 0 7px;
+}
+
+ul.search li a {
+ font-weight: bold;
+}
+
+ul.search li p.context {
+ color: #888;
+ margin: 2px 0 0 30px;
+ text-align: left;
+}
+
+ul.keywordmatches li.goodmatch a {
+ font-weight: bold;
+}
+
+/* -- index page ------------------------------------------------------------ */
+
+table.contentstable {
+ width: 90%;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table.contentstable p.biglink {
+ line-height: 150%;
+}
+
+a.biglink {
+ font-size: 1.3em;
+}
+
+span.linkdescr {
+ font-style: italic;
+ padding-top: 5px;
+ font-size: 90%;
+}
+
+/* -- general index --------------------------------------------------------- */
+
+table.indextable {
+ width: 100%;
+}
+
+table.indextable td {
+ text-align: left;
+ vertical-align: top;
+}
+
+table.indextable ul {
+ margin-top: 0;
+ margin-bottom: 0;
+ list-style-type: none;
+}
+
+table.indextable > tbody > tr > td > ul {
+ padding-left: 0em;
+}
+
+table.indextable tr.pcap {
+ height: 10px;
+}
+
+table.indextable tr.cap {
+ margin-top: 10px;
+ background-color: #f2f2f2;
+}
+
+img.toggler {
+ margin-right: 3px;
+ margin-top: 3px;
+ cursor: pointer;
+}
+
+div.modindex-jumpbox {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ margin: 1em 0 1em 0;
+ padding: 0.4em;
+}
+
+div.genindex-jumpbox {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ margin: 1em 0 1em 0;
+ padding: 0.4em;
+}
+
+/* -- domain module index --------------------------------------------------- */
+
+table.modindextable td {
+ padding: 2px;
+ border-collapse: collapse;
+}
+
+/* -- general body styles --------------------------------------------------- */
+
+div.body {
+ min-width: 360px;
+ max-width: 800px;
+}
+
+div.body p, div.body dd, div.body li, div.body blockquote {
+ -moz-hyphens: auto;
+ -ms-hyphens: auto;
+ -webkit-hyphens: auto;
+ hyphens: auto;
+}
+
+a.headerlink {
+ visibility: hidden;
+}
+
+a:visited {
+ color: #551A8B;
+}
+
+h1:hover > a.headerlink,
+h2:hover > a.headerlink,
+h3:hover > a.headerlink,
+h4:hover > a.headerlink,
+h5:hover > a.headerlink,
+h6:hover > a.headerlink,
+dt:hover > a.headerlink,
+caption:hover > a.headerlink,
+p.caption:hover > a.headerlink,
+div.code-block-caption:hover > a.headerlink {
+ visibility: visible;
+}
+
+div.body p.caption {
+ text-align: inherit;
+}
+
+div.body td {
+ text-align: left;
+}
+
+.first {
+ margin-top: 0 !important;
+}
+
+p.rubric {
+ margin-top: 30px;
+ font-weight: bold;
+}
+
+img.align-left, figure.align-left, .figure.align-left, object.align-left {
+ clear: left;
+ float: left;
+ margin-right: 1em;
+}
+
+img.align-right, figure.align-right, .figure.align-right, object.align-right {
+ clear: right;
+ float: right;
+ margin-left: 1em;
+}
+
+img.align-center, figure.align-center, .figure.align-center, object.align-center {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+img.align-default, figure.align-default, .figure.align-default {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.align-left {
+ text-align: left;
+}
+
+.align-center {
+ text-align: center;
+}
+
+.align-default {
+ text-align: center;
+}
+
+.align-right {
+ text-align: right;
+}
+
+/* -- sidebars -------------------------------------------------------------- */
+
+div.sidebar,
+aside.sidebar {
+ margin: 0 0 0.5em 1em;
+ border: 1px solid #ddb;
+ padding: 7px;
+ background-color: #ffe;
+ width: 40%;
+ float: right;
+ clear: right;
+ overflow-x: auto;
+}
+
+p.sidebar-title {
+ font-weight: bold;
+}
+
+nav.contents,
+aside.topic,
+div.admonition, div.topic, blockquote {
+ clear: left;
+}
+
+/* -- topics ---------------------------------------------------------------- */
+
+nav.contents,
+aside.topic,
+div.topic {
+ border: 1px solid #ccc;
+ padding: 7px;
+ margin: 10px 0 10px 0;
+}
+
+p.topic-title {
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-top: 10px;
+}
+
+/* -- admonitions ----------------------------------------------------------- */
+
+div.admonition {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ padding: 7px;
+}
+
+div.admonition dt {
+ font-weight: bold;
+}
+
+p.admonition-title {
+ margin: 0px 10px 5px 0px;
+ font-weight: bold;
+}
+
+div.body p.centered {
+ text-align: center;
+ margin-top: 25px;
+}
+
+/* -- content of sidebars/topics/admonitions -------------------------------- */
+
+div.sidebar > :last-child,
+aside.sidebar > :last-child,
+nav.contents > :last-child,
+aside.topic > :last-child,
+div.topic > :last-child,
+div.admonition > :last-child {
+ margin-bottom: 0;
+}
+
+div.sidebar::after,
+aside.sidebar::after,
+nav.contents::after,
+aside.topic::after,
+div.topic::after,
+div.admonition::after,
+blockquote::after {
+ display: block;
+ content: '';
+ clear: both;
+}
+
+/* -- tables ---------------------------------------------------------------- */
+
+table.docutils {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ border: 0;
+ border-collapse: collapse;
+}
+
+table.align-center {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table.align-default {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table caption span.caption-number {
+ font-style: italic;
+}
+
+table caption span.caption-text {
+}
+
+table.docutils td, table.docutils th {
+ padding: 1px 8px 1px 5px;
+ border-top: 0;
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 1px solid #aaa;
+}
+
+th {
+ text-align: left;
+ padding-right: 5px;
+}
+
+table.citation {
+ border-left: solid 1px gray;
+ margin-left: 1px;
+}
+
+table.citation td {
+ border-bottom: none;
+}
+
+th > :first-child,
+td > :first-child {
+ margin-top: 0px;
+}
+
+th > :last-child,
+td > :last-child {
+ margin-bottom: 0px;
+}
+
+/* -- figures --------------------------------------------------------------- */
+
+div.figure, figure {
+ margin: 0.5em;
+ padding: 0.5em;
+}
+
+div.figure p.caption, figcaption {
+ padding: 0.3em;
+}
+
+div.figure p.caption span.caption-number,
+figcaption span.caption-number {
+ font-style: italic;
+}
+
+div.figure p.caption span.caption-text,
+figcaption span.caption-text {
+}
+
+/* -- field list styles ----------------------------------------------------- */
+
+table.field-list td, table.field-list th {
+ border: 0 !important;
+}
+
+.field-list ul {
+ margin: 0;
+ padding-left: 1em;
+}
+
+.field-list p {
+ margin: 0;
+}
+
+.field-name {
+ -moz-hyphens: manual;
+ -ms-hyphens: manual;
+ -webkit-hyphens: manual;
+ hyphens: manual;
+}
+
+/* -- hlist styles ---------------------------------------------------------- */
+
+table.hlist {
+ margin: 1em 0;
+}
+
+table.hlist td {
+ vertical-align: top;
+}
+
+/* -- object description styles --------------------------------------------- */
+
+.sig {
+ font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+}
+
+.sig-name, code.descname {
+ background-color: transparent;
+ font-weight: bold;
+}
+
+.sig-name {
+ font-size: 1.1em;
+}
+
+code.descname {
+ font-size: 1.2em;
+}
+
+.sig-prename, code.descclassname {
+ background-color: transparent;
+}
+
+.optional {
+ font-size: 1.3em;
+}
+
+.sig-paren {
+ font-size: larger;
+}
+
+.sig-param.n {
+ font-style: italic;
+}
+
+/* C++ specific styling */
+
+.sig-inline.c-texpr,
+.sig-inline.cpp-texpr {
+ font-family: unset;
+}
+
+.sig.c .k, .sig.c .kt,
+.sig.cpp .k, .sig.cpp .kt {
+ color: #0033B3;
+}
+
+.sig.c .m,
+.sig.cpp .m {
+ color: #1750EB;
+}
+
+.sig.c .s, .sig.c .sc,
+.sig.cpp .s, .sig.cpp .sc {
+ color: #067D17;
+}
+
+
+/* -- other body styles ----------------------------------------------------- */
+
+ol.arabic {
+ list-style: decimal;
+}
+
+ol.loweralpha {
+ list-style: lower-alpha;
+}
+
+ol.upperalpha {
+ list-style: upper-alpha;
+}
+
+ol.lowerroman {
+ list-style: lower-roman;
+}
+
+ol.upperroman {
+ list-style: upper-roman;
+}
+
+:not(li) > ol > li:first-child > :first-child,
+:not(li) > ul > li:first-child > :first-child {
+ margin-top: 0px;
+}
+
+:not(li) > ol > li:last-child > :last-child,
+:not(li) > ul > li:last-child > :last-child {
+ margin-bottom: 0px;
+}
+
+ol.simple ol p,
+ol.simple ul p,
+ul.simple ol p,
+ul.simple ul p {
+ margin-top: 0;
+}
+
+ol.simple > li:not(:first-child) > p,
+ul.simple > li:not(:first-child) > p {
+ margin-top: 0;
+}
+
+ol.simple p,
+ul.simple p {
+ margin-bottom: 0;
+}
+
+aside.footnote > span,
+div.citation > span {
+ float: left;
+}
+aside.footnote > span:last-of-type,
+div.citation > span:last-of-type {
+ padding-right: 0.5em;
+}
+aside.footnote > p {
+ margin-left: 2em;
+}
+div.citation > p {
+ margin-left: 4em;
+}
+aside.footnote > p:last-of-type,
+div.citation > p:last-of-type {
+ margin-bottom: 0em;
+}
+aside.footnote > p:last-of-type:after,
+div.citation > p:last-of-type:after {
+ content: "";
+ clear: both;
+}
+
+dl.field-list {
+ display: grid;
+ grid-template-columns: fit-content(30%) auto;
+}
+
+dl.field-list > dt {
+ font-weight: bold;
+ word-break: break-word;
+ padding-left: 0.5em;
+ padding-right: 5px;
+}
+
+dl.field-list > dd {
+ padding-left: 0.5em;
+ margin-top: 0em;
+ margin-left: 0em;
+ margin-bottom: 0em;
+}
+
+dl {
+ margin-bottom: 15px;
+}
+
+dd > :first-child {
+ margin-top: 0px;
+}
+
+dd ul, dd table {
+ margin-bottom: 10px;
+}
+
+dd {
+ margin-top: 3px;
+ margin-bottom: 10px;
+ margin-left: 30px;
+}
+
+.sig dd {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+.sig dl {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+dl > dd:last-child,
+dl > dd:last-child > :last-child {
+ margin-bottom: 0;
+}
+
+dt:target, span.highlighted {
+ background-color: #fbe54e;
+}
+
+rect.highlighted {
+ fill: #fbe54e;
+}
+
+dl.glossary dt {
+ font-weight: bold;
+ font-size: 1.1em;
+}
+
+.versionmodified {
+ font-style: italic;
+}
+
+.system-message {
+ background-color: #fda;
+ padding: 5px;
+ border: 3px solid red;
+}
+
+.footnote:target {
+ background-color: #ffa;
+}
+
+.line-block {
+ display: block;
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+.line-block .line-block {
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-left: 1.5em;
+}
+
+.guilabel, .menuselection {
+ font-family: sans-serif;
+}
+
+.accelerator {
+ text-decoration: underline;
+}
+
+.classifier {
+ font-style: oblique;
+}
+
+.classifier:before {
+ font-style: normal;
+ margin: 0 0.5em;
+ content: ":";
+ display: inline-block;
+}
+
+abbr, acronym {
+ border-bottom: dotted 1px;
+ cursor: help;
+}
+
+.translated {
+ background-color: rgba(207, 255, 207, 0.2)
+}
+
+.untranslated {
+ background-color: rgba(255, 207, 207, 0.2)
+}
+
+/* -- code displays --------------------------------------------------------- */
+
+pre {
+ overflow: auto;
+ overflow-y: hidden; /* fixes display issues on Chrome browsers */
+}
+
+pre, div[class*="highlight-"] {
+ clear: both;
+}
+
+span.pre {
+ -moz-hyphens: none;
+ -ms-hyphens: none;
+ -webkit-hyphens: none;
+ hyphens: none;
+ white-space: nowrap;
+}
+
+div[class*="highlight-"] {
+ margin: 1em 0;
+}
+
+td.linenos pre {
+ border: 0;
+ background-color: transparent;
+ color: #aaa;
+}
+
+table.highlighttable {
+ display: block;
+}
+
+table.highlighttable tbody {
+ display: block;
+}
+
+table.highlighttable tr {
+ display: flex;
+}
+
+table.highlighttable td {
+ margin: 0;
+ padding: 0;
+}
+
+table.highlighttable td.linenos {
+ padding-right: 0.5em;
+}
+
+table.highlighttable td.code {
+ flex: 1;
+ overflow: hidden;
+}
+
+.highlight .hll {
+ display: block;
+}
+
+div.highlight pre,
+table.highlighttable pre {
+ margin: 0;
+}
+
+div.code-block-caption + div {
+ margin-top: 0;
+}
+
+div.code-block-caption {
+ margin-top: 1em;
+ padding: 2px 5px;
+ font-size: small;
+}
+
+div.code-block-caption code {
+ background-color: transparent;
+}
+
+table.highlighttable td.linenos,
+span.linenos,
+div.highlight span.gp { /* gp: Generic.Prompt */
+ user-select: none;
+ -webkit-user-select: text; /* Safari fallback only */
+ -webkit-user-select: none; /* Chrome/Safari */
+ -moz-user-select: none; /* Firefox */
+ -ms-user-select: none; /* IE10+ */
+}
+
+div.code-block-caption span.caption-number {
+ padding: 0.1em 0.3em;
+ font-style: italic;
+}
+
+div.code-block-caption span.caption-text {
+}
+
+div.literal-block-wrapper {
+ margin: 1em 0;
+}
+
+code.xref, a code {
+ background-color: transparent;
+ font-weight: bold;
+}
+
+h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
+ background-color: transparent;
+}
+
+.viewcode-link {
+ float: right;
+}
+
+.viewcode-back {
+ float: right;
+ font-family: sans-serif;
+}
+
+div.viewcode-block:target {
+ margin: -1px -10px;
+ padding: 0 10px;
+}
+
+/* -- math display ---------------------------------------------------------- */
+
+img.math {
+ vertical-align: middle;
+}
+
+div.body div.math p {
+ text-align: center;
+}
+
+span.eqno {
+ float: right;
+}
+
+span.eqno a.headerlink {
+ position: absolute;
+ z-index: 1;
+}
+
+div.math:hover a.headerlink {
+ visibility: visible;
+}
+
+/* -- printout stylesheet --------------------------------------------------- */
+
+@media print {
+ div.document,
+ div.documentwrapper,
+ div.bodywrapper {
+ margin: 0 !important;
+ width: 100%;
+ }
+
+ div.sphinxsidebar,
+ div.related,
+ div.footer,
+ #top-link {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/branch/main/singlehtml/_static/check-solid.svg b/branch/main/singlehtml/_static/check-solid.svg
new file mode 100644
index 0000000..92fad4b
--- /dev/null
+++ b/branch/main/singlehtml/_static/check-solid.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/branch/main/singlehtml/_static/clipboard.min.js b/branch/main/singlehtml/_static/clipboard.min.js
new file mode 100644
index 0000000..54b3c46
--- /dev/null
+++ b/branch/main/singlehtml/_static/clipboard.min.js
@@ -0,0 +1,7 @@
+/*!
+ * clipboard.js v2.0.8
+ * https://clipboardjs.com/
+ *
+ * Licensed MIT © Zeno Rocha
+ */
+!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1
+
+
+
+
diff --git a/branch/main/singlehtml/_static/copybutton.css b/branch/main/singlehtml/_static/copybutton.css
new file mode 100644
index 0000000..f1916ec
--- /dev/null
+++ b/branch/main/singlehtml/_static/copybutton.css
@@ -0,0 +1,94 @@
+/* Copy buttons */
+button.copybtn {
+ position: absolute;
+ display: flex;
+ top: .3em;
+ right: .3em;
+ width: 1.7em;
+ height: 1.7em;
+ opacity: 0;
+ transition: opacity 0.3s, border .3s, background-color .3s;
+ user-select: none;
+ padding: 0;
+ border: none;
+ outline: none;
+ border-radius: 0.4em;
+ /* The colors that GitHub uses */
+ border: #1b1f2426 1px solid;
+ background-color: #f6f8fa;
+ color: #57606a;
+}
+
+button.copybtn.success {
+ border-color: #22863a;
+ color: #22863a;
+}
+
+button.copybtn svg {
+ stroke: currentColor;
+ width: 1.5em;
+ height: 1.5em;
+ padding: 0.1em;
+}
+
+div.highlight {
+ position: relative;
+}
+
+/* Show the copybutton */
+.highlight:hover button.copybtn, button.copybtn.success {
+ opacity: 1;
+}
+
+.highlight button.copybtn:hover {
+ background-color: rgb(235, 235, 235);
+}
+
+.highlight button.copybtn:active {
+ background-color: rgb(187, 187, 187);
+}
+
+/**
+ * A minimal CSS-only tooltip copied from:
+ * https://codepen.io/mildrenben/pen/rVBrpK
+ *
+ * To use, write HTML like the following:
+ *
+ * Short
+ */
+ .o-tooltip--left {
+ position: relative;
+ }
+
+ .o-tooltip--left:after {
+ opacity: 0;
+ visibility: hidden;
+ position: absolute;
+ content: attr(data-tooltip);
+ padding: .2em;
+ font-size: .8em;
+ left: -.2em;
+ background: grey;
+ color: white;
+ white-space: nowrap;
+ z-index: 2;
+ border-radius: 2px;
+ transform: translateX(-102%) translateY(0);
+ transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1);
+}
+
+.o-tooltip--left:hover:after {
+ display: block;
+ opacity: 1;
+ visibility: visible;
+ transform: translateX(-100%) translateY(0);
+ transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1);
+ transition-delay: .5s;
+}
+
+/* By default the copy button shouldn't show up when printing a page */
+@media print {
+ button.copybtn {
+ display: none;
+ }
+}
diff --git a/branch/main/singlehtml/_static/copybutton.js b/branch/main/singlehtml/_static/copybutton.js
new file mode 100644
index 0000000..b398703
--- /dev/null
+++ b/branch/main/singlehtml/_static/copybutton.js
@@ -0,0 +1,248 @@
+// Localization support
+const messages = {
+ 'en': {
+ 'copy': 'Copy',
+ 'copy_to_clipboard': 'Copy to clipboard',
+ 'copy_success': 'Copied!',
+ 'copy_failure': 'Failed to copy',
+ },
+ 'es' : {
+ 'copy': 'Copiar',
+ 'copy_to_clipboard': 'Copiar al portapapeles',
+ 'copy_success': '¡Copiado!',
+ 'copy_failure': 'Error al copiar',
+ },
+ 'de' : {
+ 'copy': 'Kopieren',
+ 'copy_to_clipboard': 'In die Zwischenablage kopieren',
+ 'copy_success': 'Kopiert!',
+ 'copy_failure': 'Fehler beim Kopieren',
+ },
+ 'fr' : {
+ 'copy': 'Copier',
+ 'copy_to_clipboard': 'Copier dans le presse-papier',
+ 'copy_success': 'Copié !',
+ 'copy_failure': 'Échec de la copie',
+ },
+ 'ru': {
+ 'copy': 'Скопировать',
+ 'copy_to_clipboard': 'Скопировать в буфер',
+ 'copy_success': 'Скопировано!',
+ 'copy_failure': 'Не удалось скопировать',
+ },
+ 'zh-CN': {
+ 'copy': '复制',
+ 'copy_to_clipboard': '复制到剪贴板',
+ 'copy_success': '复制成功!',
+ 'copy_failure': '复制失败',
+ },
+ 'it' : {
+ 'copy': 'Copiare',
+ 'copy_to_clipboard': 'Copiato negli appunti',
+ 'copy_success': 'Copiato!',
+ 'copy_failure': 'Errore durante la copia',
+ }
+}
+
+let locale = 'en'
+if( document.documentElement.lang !== undefined
+ && messages[document.documentElement.lang] !== undefined ) {
+ locale = document.documentElement.lang
+}
+
+let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT;
+if (doc_url_root == '#') {
+ doc_url_root = '';
+}
+
+/**
+ * SVG files for our copy buttons
+ */
+let iconCheck = `
+ ${messages[locale]['copy_success']}
+
+
+ `
+
+// If the user specified their own SVG use that, otherwise use the default
+let iconCopy = ``;
+if (!iconCopy) {
+ iconCopy = `
+ ${messages[locale]['copy_to_clipboard']}
+
+
+
+ `
+}
+
+/**
+ * Set up copy/paste for code blocks
+ */
+
+const runWhenDOMLoaded = cb => {
+ if (document.readyState != 'loading') {
+ cb()
+ } else if (document.addEventListener) {
+ document.addEventListener('DOMContentLoaded', cb)
+ } else {
+ document.attachEvent('onreadystatechange', function() {
+ if (document.readyState == 'complete') cb()
+ })
+ }
+}
+
+const codeCellId = index => `codecell${index}`
+
+// Clears selected text since ClipboardJS will select the text when copying
+const clearSelection = () => {
+ if (window.getSelection) {
+ window.getSelection().removeAllRanges()
+ } else if (document.selection) {
+ document.selection.empty()
+ }
+}
+
+// Changes tooltip text for a moment, then changes it back
+// We want the timeout of our `success` class to be a bit shorter than the
+// tooltip and icon change, so that we can hide the icon before changing back.
+var timeoutIcon = 2000;
+var timeoutSuccessClass = 1500;
+
+const temporarilyChangeTooltip = (el, oldText, newText) => {
+ el.setAttribute('data-tooltip', newText)
+ el.classList.add('success')
+ // Remove success a little bit sooner than we change the tooltip
+ // So that we can use CSS to hide the copybutton first
+ setTimeout(() => el.classList.remove('success'), timeoutSuccessClass)
+ setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon)
+}
+
+// Changes the copy button icon for two seconds, then changes it back
+const temporarilyChangeIcon = (el) => {
+ el.innerHTML = iconCheck;
+ setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon)
+}
+
+const addCopyButtonToCodeCells = () => {
+ // If ClipboardJS hasn't loaded, wait a bit and try again. This
+ // happens because we load ClipboardJS asynchronously.
+ if (window.ClipboardJS === undefined) {
+ setTimeout(addCopyButtonToCodeCells, 250)
+ return
+ }
+
+ // Add copybuttons to all of our code cells
+ const COPYBUTTON_SELECTOR = 'div.highlight pre';
+ const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR)
+ codeCells.forEach((codeCell, index) => {
+ const id = codeCellId(index)
+ codeCell.setAttribute('id', id)
+
+ const clipboardButton = id =>
+ `
+ ${iconCopy}
+ `
+ codeCell.insertAdjacentHTML('afterend', clipboardButton(id))
+ })
+
+function escapeRegExp(string) {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
+}
+
+/**
+ * Removes excluded text from a Node.
+ *
+ * @param {Node} target Node to filter.
+ * @param {string} exclude CSS selector of nodes to exclude.
+ * @returns {DOMString} Text from `target` with text removed.
+ */
+function filterText(target, exclude) {
+ const clone = target.cloneNode(true); // clone as to not modify the live DOM
+ if (exclude) {
+ // remove excluded nodes
+ clone.querySelectorAll(exclude).forEach(node => node.remove());
+ }
+ return clone.innerText;
+}
+
+// Callback when a copy button is clicked. Will be passed the node that was clicked
+// should then grab the text and replace pieces of text that shouldn't be used in output
+function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") {
+ var regexp;
+ var match;
+
+ // Do we check for line continuation characters and "HERE-documents"?
+ var useLineCont = !!lineContinuationChar
+ var useHereDoc = !!hereDocDelim
+
+ // create regexp to capture prompt and remaining line
+ if (isRegexp) {
+ regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)')
+ } else {
+ regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)')
+ }
+
+ const outputLines = [];
+ var promptFound = false;
+ var gotLineCont = false;
+ var gotHereDoc = false;
+ const lineGotPrompt = [];
+ for (const line of textContent.split('\n')) {
+ match = line.match(regexp)
+ if (match || gotLineCont || gotHereDoc) {
+ promptFound = regexp.test(line)
+ lineGotPrompt.push(promptFound)
+ if (removePrompts && promptFound) {
+ outputLines.push(match[2])
+ } else {
+ outputLines.push(line)
+ }
+ gotLineCont = line.endsWith(lineContinuationChar) & useLineCont
+ if (line.includes(hereDocDelim) & useHereDoc)
+ gotHereDoc = !gotHereDoc
+ } else if (!onlyCopyPromptLines) {
+ outputLines.push(line)
+ } else if (copyEmptyLines && line.trim() === '') {
+ outputLines.push(line)
+ }
+ }
+
+ // If no lines with the prompt were found then just use original lines
+ if (lineGotPrompt.some(v => v === true)) {
+ textContent = outputLines.join('\n');
+ }
+
+ // Remove a trailing newline to avoid auto-running when pasting
+ if (textContent.endsWith("\n")) {
+ textContent = textContent.slice(0, -1)
+ }
+ return textContent
+}
+
+
+var copyTargetText = (trigger) => {
+ var target = document.querySelector(trigger.attributes['data-clipboard-target'].value);
+
+ // get filtered text
+ let exclude = '.linenos, .gp';
+
+ let text = filterText(target, exclude);
+ return formatCopyText(text, '', false, true, true, true, '', '')
+}
+
+ // Initialize with a callback so we can modify the text before copy
+ const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText})
+
+ // Update UI with error/success messages
+ clipboard.on('success', event => {
+ clearSelection()
+ temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success'])
+ temporarilyChangeIcon(event.trigger)
+ })
+
+ clipboard.on('error', event => {
+ temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure'])
+ })
+}
+
+runWhenDOMLoaded(addCopyButtonToCodeCells)
\ No newline at end of file
diff --git a/branch/main/singlehtml/_static/copybutton_funcs.js b/branch/main/singlehtml/_static/copybutton_funcs.js
new file mode 100644
index 0000000..dbe1aaa
--- /dev/null
+++ b/branch/main/singlehtml/_static/copybutton_funcs.js
@@ -0,0 +1,73 @@
+function escapeRegExp(string) {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
+}
+
+/**
+ * Removes excluded text from a Node.
+ *
+ * @param {Node} target Node to filter.
+ * @param {string} exclude CSS selector of nodes to exclude.
+ * @returns {DOMString} Text from `target` with text removed.
+ */
+export function filterText(target, exclude) {
+ const clone = target.cloneNode(true); // clone as to not modify the live DOM
+ if (exclude) {
+ // remove excluded nodes
+ clone.querySelectorAll(exclude).forEach(node => node.remove());
+ }
+ return clone.innerText;
+}
+
+// Callback when a copy button is clicked. Will be passed the node that was clicked
+// should then grab the text and replace pieces of text that shouldn't be used in output
+export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") {
+ var regexp;
+ var match;
+
+ // Do we check for line continuation characters and "HERE-documents"?
+ var useLineCont = !!lineContinuationChar
+ var useHereDoc = !!hereDocDelim
+
+ // create regexp to capture prompt and remaining line
+ if (isRegexp) {
+ regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)')
+ } else {
+ regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)')
+ }
+
+ const outputLines = [];
+ var promptFound = false;
+ var gotLineCont = false;
+ var gotHereDoc = false;
+ const lineGotPrompt = [];
+ for (const line of textContent.split('\n')) {
+ match = line.match(regexp)
+ if (match || gotLineCont || gotHereDoc) {
+ promptFound = regexp.test(line)
+ lineGotPrompt.push(promptFound)
+ if (removePrompts && promptFound) {
+ outputLines.push(match[2])
+ } else {
+ outputLines.push(line)
+ }
+ gotLineCont = line.endsWith(lineContinuationChar) & useLineCont
+ if (line.includes(hereDocDelim) & useHereDoc)
+ gotHereDoc = !gotHereDoc
+ } else if (!onlyCopyPromptLines) {
+ outputLines.push(line)
+ } else if (copyEmptyLines && line.trim() === '') {
+ outputLines.push(line)
+ }
+ }
+
+ // If no lines with the prompt were found then just use original lines
+ if (lineGotPrompt.some(v => v === true)) {
+ textContent = outputLines.join('\n');
+ }
+
+ // Remove a trailing newline to avoid auto-running when pasting
+ if (textContent.endsWith("\n")) {
+ textContent = textContent.slice(0, -1)
+ }
+ return textContent
+}
diff --git a/branch/main/singlehtml/_static/css/badge_only.css b/branch/main/singlehtml/_static/css/badge_only.css
new file mode 100644
index 0000000..88ba55b
--- /dev/null
+++ b/branch/main/singlehtml/_static/css/badge_only.css
@@ -0,0 +1 @@
+.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}
\ No newline at end of file
diff --git a/branch/main/singlehtml/_static/css/fonts/Roboto-Slab-Bold.woff b/branch/main/singlehtml/_static/css/fonts/Roboto-Slab-Bold.woff
new file mode 100644
index 0000000..6cb6000
Binary files /dev/null and b/branch/main/singlehtml/_static/css/fonts/Roboto-Slab-Bold.woff differ
diff --git a/branch/main/singlehtml/_static/css/fonts/Roboto-Slab-Bold.woff2 b/branch/main/singlehtml/_static/css/fonts/Roboto-Slab-Bold.woff2
new file mode 100644
index 0000000..7059e23
Binary files /dev/null and b/branch/main/singlehtml/_static/css/fonts/Roboto-Slab-Bold.woff2 differ
diff --git a/branch/main/singlehtml/_static/css/fonts/Roboto-Slab-Regular.woff b/branch/main/singlehtml/_static/css/fonts/Roboto-Slab-Regular.woff
new file mode 100644
index 0000000..f815f63
Binary files /dev/null and b/branch/main/singlehtml/_static/css/fonts/Roboto-Slab-Regular.woff differ
diff --git a/branch/main/singlehtml/_static/css/fonts/Roboto-Slab-Regular.woff2 b/branch/main/singlehtml/_static/css/fonts/Roboto-Slab-Regular.woff2
new file mode 100644
index 0000000..f2c76e5
Binary files /dev/null and b/branch/main/singlehtml/_static/css/fonts/Roboto-Slab-Regular.woff2 differ
diff --git a/branch/main/singlehtml/_static/css/fonts/fontawesome-webfont.eot b/branch/main/singlehtml/_static/css/fonts/fontawesome-webfont.eot
new file mode 100644
index 0000000..e9f60ca
Binary files /dev/null and b/branch/main/singlehtml/_static/css/fonts/fontawesome-webfont.eot differ
diff --git a/branch/main/singlehtml/_static/css/fonts/fontawesome-webfont.svg b/branch/main/singlehtml/_static/css/fonts/fontawesome-webfont.svg
new file mode 100644
index 0000000..855c845
--- /dev/null
+++ b/branch/main/singlehtml/_static/css/fonts/fontawesome-webfont.svg
@@ -0,0 +1,2671 @@
+
+
+
+
+Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016
+ By ,,,
+Copyright Dave Gandy 2016. All rights reserved.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/branch/main/singlehtml/_static/css/fonts/fontawesome-webfont.ttf b/branch/main/singlehtml/_static/css/fonts/fontawesome-webfont.ttf
new file mode 100644
index 0000000..35acda2
Binary files /dev/null and b/branch/main/singlehtml/_static/css/fonts/fontawesome-webfont.ttf differ
diff --git a/branch/main/singlehtml/_static/css/fonts/fontawesome-webfont.woff b/branch/main/singlehtml/_static/css/fonts/fontawesome-webfont.woff
new file mode 100644
index 0000000..400014a
Binary files /dev/null and b/branch/main/singlehtml/_static/css/fonts/fontawesome-webfont.woff differ
diff --git a/branch/main/singlehtml/_static/css/fonts/fontawesome-webfont.woff2 b/branch/main/singlehtml/_static/css/fonts/fontawesome-webfont.woff2
new file mode 100644
index 0000000..4d13fc6
Binary files /dev/null and b/branch/main/singlehtml/_static/css/fonts/fontawesome-webfont.woff2 differ
diff --git a/branch/main/singlehtml/_static/css/fonts/lato-bold-italic.woff b/branch/main/singlehtml/_static/css/fonts/lato-bold-italic.woff
new file mode 100644
index 0000000..88ad05b
Binary files /dev/null and b/branch/main/singlehtml/_static/css/fonts/lato-bold-italic.woff differ
diff --git a/branch/main/singlehtml/_static/css/fonts/lato-bold-italic.woff2 b/branch/main/singlehtml/_static/css/fonts/lato-bold-italic.woff2
new file mode 100644
index 0000000..c4e3d80
Binary files /dev/null and b/branch/main/singlehtml/_static/css/fonts/lato-bold-italic.woff2 differ
diff --git a/branch/main/singlehtml/_static/css/fonts/lato-bold.woff b/branch/main/singlehtml/_static/css/fonts/lato-bold.woff
new file mode 100644
index 0000000..c6dff51
Binary files /dev/null and b/branch/main/singlehtml/_static/css/fonts/lato-bold.woff differ
diff --git a/branch/main/singlehtml/_static/css/fonts/lato-bold.woff2 b/branch/main/singlehtml/_static/css/fonts/lato-bold.woff2
new file mode 100644
index 0000000..bb19504
Binary files /dev/null and b/branch/main/singlehtml/_static/css/fonts/lato-bold.woff2 differ
diff --git a/branch/main/singlehtml/_static/css/fonts/lato-normal-italic.woff b/branch/main/singlehtml/_static/css/fonts/lato-normal-italic.woff
new file mode 100644
index 0000000..76114bc
Binary files /dev/null and b/branch/main/singlehtml/_static/css/fonts/lato-normal-italic.woff differ
diff --git a/branch/main/singlehtml/_static/css/fonts/lato-normal-italic.woff2 b/branch/main/singlehtml/_static/css/fonts/lato-normal-italic.woff2
new file mode 100644
index 0000000..3404f37
Binary files /dev/null and b/branch/main/singlehtml/_static/css/fonts/lato-normal-italic.woff2 differ
diff --git a/branch/main/singlehtml/_static/css/fonts/lato-normal.woff b/branch/main/singlehtml/_static/css/fonts/lato-normal.woff
new file mode 100644
index 0000000..ae1307f
Binary files /dev/null and b/branch/main/singlehtml/_static/css/fonts/lato-normal.woff differ
diff --git a/branch/main/singlehtml/_static/css/fonts/lato-normal.woff2 b/branch/main/singlehtml/_static/css/fonts/lato-normal.woff2
new file mode 100644
index 0000000..3bf9843
Binary files /dev/null and b/branch/main/singlehtml/_static/css/fonts/lato-normal.woff2 differ
diff --git a/branch/main/singlehtml/_static/css/theme.css b/branch/main/singlehtml/_static/css/theme.css
new file mode 100644
index 0000000..0f14f10
--- /dev/null
+++ b/branch/main/singlehtml/_static/css/theme.css
@@ -0,0 +1,4 @@
+html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*!
+ * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
+ * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search .wy-dropdown>aactive,.wy-side-nav-search .wy-dropdown>afocus,.wy-side-nav-search>a:hover,.wy-side-nav-search>aactive,.wy-side-nav-search>afocus{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon,.wy-side-nav-search>a.icon{display:block}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.switch-menus{position:relative;display:block;margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-side-nav-search>div.switch-menus>div.language-switch,.wy-side-nav-search>div.switch-menus>div.version-switch{display:inline-block;padding:.2em}.wy-side-nav-search>div.switch-menus>div.language-switch select,.wy-side-nav-search>div.switch-menus>div.version-switch select{display:inline-block;margin-right:-2rem;padding-right:2rem;max-width:240px;text-align-last:center;background:none;border:none;border-radius:0;box-shadow:none;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-size:1em;font-weight:400;color:hsla(0,0%,100%,.3);cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none}.wy-side-nav-search>div.switch-menus>div.language-switch select:active,.wy-side-nav-search>div.switch-menus>div.language-switch select:focus,.wy-side-nav-search>div.switch-menus>div.language-switch select:hover,.wy-side-nav-search>div.switch-menus>div.version-switch select:active,.wy-side-nav-search>div.switch-menus>div.version-switch select:focus,.wy-side-nav-search>div.switch-menus>div.version-switch select:hover{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.wy-side-nav-search>div.switch-menus>div.language-switch select option,.wy-side-nav-search>div.switch-menus>div.version-switch select option{color:#000}.wy-side-nav-search>div.switch-menus>div.language-switch:has(>select):after,.wy-side-nav-search>div.switch-menus>div.version-switch:has(>select):after{display:inline-block;width:1.5em;height:100%;padding:.1em;content:"\f0d7";font-size:1em;line-height:1.2em;font-family:FontAwesome;text-align:center;pointer-events:none;box-sizing:border-box}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block}
\ No newline at end of file
diff --git a/branch/main/singlehtml/_static/doctools.js b/branch/main/singlehtml/_static/doctools.js
new file mode 100644
index 0000000..4d67807
--- /dev/null
+++ b/branch/main/singlehtml/_static/doctools.js
@@ -0,0 +1,156 @@
+/*
+ * doctools.js
+ * ~~~~~~~~~~~
+ *
+ * Base JavaScript utilities for all Sphinx HTML documentation.
+ *
+ * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+"use strict";
+
+const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([
+ "TEXTAREA",
+ "INPUT",
+ "SELECT",
+ "BUTTON",
+]);
+
+const _ready = (callback) => {
+ if (document.readyState !== "loading") {
+ callback();
+ } else {
+ document.addEventListener("DOMContentLoaded", callback);
+ }
+};
+
+/**
+ * Small JavaScript module for the documentation.
+ */
+const Documentation = {
+ init: () => {
+ Documentation.initDomainIndexTable();
+ Documentation.initOnKeyListeners();
+ },
+
+ /**
+ * i18n support
+ */
+ TRANSLATIONS: {},
+ PLURAL_EXPR: (n) => (n === 1 ? 0 : 1),
+ LOCALE: "unknown",
+
+ // gettext and ngettext don't access this so that the functions
+ // can safely bound to a different name (_ = Documentation.gettext)
+ gettext: (string) => {
+ const translated = Documentation.TRANSLATIONS[string];
+ switch (typeof translated) {
+ case "undefined":
+ return string; // no translation
+ case "string":
+ return translated; // translation exists
+ default:
+ return translated[0]; // (singular, plural) translation tuple exists
+ }
+ },
+
+ ngettext: (singular, plural, n) => {
+ const translated = Documentation.TRANSLATIONS[singular];
+ if (typeof translated !== "undefined")
+ return translated[Documentation.PLURAL_EXPR(n)];
+ return n === 1 ? singular : plural;
+ },
+
+ addTranslations: (catalog) => {
+ Object.assign(Documentation.TRANSLATIONS, catalog.messages);
+ Documentation.PLURAL_EXPR = new Function(
+ "n",
+ `return (${catalog.plural_expr})`
+ );
+ Documentation.LOCALE = catalog.locale;
+ },
+
+ /**
+ * helper function to focus on search bar
+ */
+ focusSearchBar: () => {
+ document.querySelectorAll("input[name=q]")[0]?.focus();
+ },
+
+ /**
+ * Initialise the domain index toggle buttons
+ */
+ initDomainIndexTable: () => {
+ const toggler = (el) => {
+ const idNumber = el.id.substr(7);
+ const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`);
+ if (el.src.substr(-9) === "minus.png") {
+ el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`;
+ toggledRows.forEach((el) => (el.style.display = "none"));
+ } else {
+ el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`;
+ toggledRows.forEach((el) => (el.style.display = ""));
+ }
+ };
+
+ const togglerElements = document.querySelectorAll("img.toggler");
+ togglerElements.forEach((el) =>
+ el.addEventListener("click", (event) => toggler(event.currentTarget))
+ );
+ togglerElements.forEach((el) => (el.style.display = ""));
+ if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler);
+ },
+
+ initOnKeyListeners: () => {
+ // only install a listener if it is really needed
+ if (
+ !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS &&
+ !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS
+ )
+ return;
+
+ document.addEventListener("keydown", (event) => {
+ // bail for input elements
+ if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
+ // bail with special keys
+ if (event.altKey || event.ctrlKey || event.metaKey) return;
+
+ if (!event.shiftKey) {
+ switch (event.key) {
+ case "ArrowLeft":
+ if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
+
+ const prevLink = document.querySelector('link[rel="prev"]');
+ if (prevLink && prevLink.href) {
+ window.location.href = prevLink.href;
+ event.preventDefault();
+ }
+ break;
+ case "ArrowRight":
+ if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
+
+ const nextLink = document.querySelector('link[rel="next"]');
+ if (nextLink && nextLink.href) {
+ window.location.href = nextLink.href;
+ event.preventDefault();
+ }
+ break;
+ }
+ }
+
+ // some keyboard layouts may need Shift to get /
+ switch (event.key) {
+ case "/":
+ if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break;
+ Documentation.focusSearchBar();
+ event.preventDefault();
+ }
+ });
+ },
+};
+
+// quick alias for translations
+const _ = Documentation.gettext;
+
+_ready(Documentation.init);
diff --git a/branch/main/singlehtml/_static/documentation_options.js b/branch/main/singlehtml/_static/documentation_options.js
new file mode 100644
index 0000000..89003c6
--- /dev/null
+++ b/branch/main/singlehtml/_static/documentation_options.js
@@ -0,0 +1,13 @@
+const DOCUMENTATION_OPTIONS = {
+ VERSION: '',
+ LANGUAGE: 'en',
+ COLLAPSE_INDEX: false,
+ BUILDER: 'singlehtml',
+ FILE_SUFFIX: '.html',
+ LINK_SUFFIX: '.html',
+ HAS_SOURCE: true,
+ SOURCELINK_SUFFIX: '.txt',
+ NAVIGATION_WITH_KEYS: false,
+ SHOW_SEARCH_SUMMARY: true,
+ ENABLE_SEARCH_SHORTCUTS: true,
+};
\ No newline at end of file
diff --git a/branch/main/singlehtml/_static/file.png b/branch/main/singlehtml/_static/file.png
new file mode 100644
index 0000000..a858a41
Binary files /dev/null and b/branch/main/singlehtml/_static/file.png differ
diff --git a/branch/main/singlehtml/_static/fonts/Lato/lato-bold.eot b/branch/main/singlehtml/_static/fonts/Lato/lato-bold.eot
new file mode 100644
index 0000000..3361183
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/Lato/lato-bold.eot differ
diff --git a/branch/main/singlehtml/_static/fonts/Lato/lato-bold.ttf b/branch/main/singlehtml/_static/fonts/Lato/lato-bold.ttf
new file mode 100644
index 0000000..29f691d
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/Lato/lato-bold.ttf differ
diff --git a/branch/main/singlehtml/_static/fonts/Lato/lato-bold.woff b/branch/main/singlehtml/_static/fonts/Lato/lato-bold.woff
new file mode 100644
index 0000000..c6dff51
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/Lato/lato-bold.woff differ
diff --git a/branch/main/singlehtml/_static/fonts/Lato/lato-bold.woff2 b/branch/main/singlehtml/_static/fonts/Lato/lato-bold.woff2
new file mode 100644
index 0000000..bb19504
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/Lato/lato-bold.woff2 differ
diff --git a/branch/main/singlehtml/_static/fonts/Lato/lato-bolditalic.eot b/branch/main/singlehtml/_static/fonts/Lato/lato-bolditalic.eot
new file mode 100644
index 0000000..3d41549
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/Lato/lato-bolditalic.eot differ
diff --git a/branch/main/singlehtml/_static/fonts/Lato/lato-bolditalic.ttf b/branch/main/singlehtml/_static/fonts/Lato/lato-bolditalic.ttf
new file mode 100644
index 0000000..f402040
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/Lato/lato-bolditalic.ttf differ
diff --git a/branch/main/singlehtml/_static/fonts/Lato/lato-bolditalic.woff b/branch/main/singlehtml/_static/fonts/Lato/lato-bolditalic.woff
new file mode 100644
index 0000000..88ad05b
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/Lato/lato-bolditalic.woff differ
diff --git a/branch/main/singlehtml/_static/fonts/Lato/lato-bolditalic.woff2 b/branch/main/singlehtml/_static/fonts/Lato/lato-bolditalic.woff2
new file mode 100644
index 0000000..c4e3d80
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/Lato/lato-bolditalic.woff2 differ
diff --git a/branch/main/singlehtml/_static/fonts/Lato/lato-italic.eot b/branch/main/singlehtml/_static/fonts/Lato/lato-italic.eot
new file mode 100644
index 0000000..3f82642
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/Lato/lato-italic.eot differ
diff --git a/branch/main/singlehtml/_static/fonts/Lato/lato-italic.ttf b/branch/main/singlehtml/_static/fonts/Lato/lato-italic.ttf
new file mode 100644
index 0000000..b4bfc9b
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/Lato/lato-italic.ttf differ
diff --git a/branch/main/singlehtml/_static/fonts/Lato/lato-italic.woff b/branch/main/singlehtml/_static/fonts/Lato/lato-italic.woff
new file mode 100644
index 0000000..76114bc
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/Lato/lato-italic.woff differ
diff --git a/branch/main/singlehtml/_static/fonts/Lato/lato-italic.woff2 b/branch/main/singlehtml/_static/fonts/Lato/lato-italic.woff2
new file mode 100644
index 0000000..3404f37
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/Lato/lato-italic.woff2 differ
diff --git a/branch/main/singlehtml/_static/fonts/Lato/lato-regular.eot b/branch/main/singlehtml/_static/fonts/Lato/lato-regular.eot
new file mode 100644
index 0000000..11e3f2a
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/Lato/lato-regular.eot differ
diff --git a/branch/main/singlehtml/_static/fonts/Lato/lato-regular.ttf b/branch/main/singlehtml/_static/fonts/Lato/lato-regular.ttf
new file mode 100644
index 0000000..74decd9
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/Lato/lato-regular.ttf differ
diff --git a/branch/main/singlehtml/_static/fonts/Lato/lato-regular.woff b/branch/main/singlehtml/_static/fonts/Lato/lato-regular.woff
new file mode 100644
index 0000000..ae1307f
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/Lato/lato-regular.woff differ
diff --git a/branch/main/singlehtml/_static/fonts/Lato/lato-regular.woff2 b/branch/main/singlehtml/_static/fonts/Lato/lato-regular.woff2
new file mode 100644
index 0000000..3bf9843
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/Lato/lato-regular.woff2 differ
diff --git a/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot
new file mode 100644
index 0000000..79dc8ef
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot differ
diff --git a/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf
new file mode 100644
index 0000000..df5d1df
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf differ
diff --git a/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff
new file mode 100644
index 0000000..6cb6000
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff differ
diff --git a/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2
new file mode 100644
index 0000000..7059e23
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 differ
diff --git a/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot
new file mode 100644
index 0000000..2f7ca78
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot differ
diff --git a/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf
new file mode 100644
index 0000000..eb52a79
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf differ
diff --git a/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff
new file mode 100644
index 0000000..f815f63
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff differ
diff --git a/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2
new file mode 100644
index 0000000..f2c76e5
Binary files /dev/null and b/branch/main/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 differ
diff --git a/branch/main/singlehtml/_static/jquery.js b/branch/main/singlehtml/_static/jquery.js
new file mode 100644
index 0000000..c4c6022
--- /dev/null
+++ b/branch/main/singlehtml/_static/jquery.js
@@ -0,0 +1,2 @@
+/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */
+!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML=" ",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML=" ";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML=" ",y.option=!!ce.lastChild;var ge={thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 "),n("table.docutils.footnote").wrap(""),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(' '),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t a.language.name.localeCompare(b.language.name));
+
+ const languagesHTML = `
+
+ Languages
+ ${languages
+ .map(
+ (translation) => `
+
+ ${translation.language.code}
+
+ `,
+ )
+ .join("\n")}
+
+ `;
+ return languagesHTML;
+ }
+
+ function renderVersions(config) {
+ if (!config.versions.active.length) {
+ return "";
+ }
+ const versionsHTML = `
+
+ Versions
+ ${config.versions.active
+ .map(
+ (version) => `
+
+ ${version.slug}
+
+ `,
+ )
+ .join("\n")}
+
+ `;
+ return versionsHTML;
+ }
+
+ function renderDownloads(config) {
+ if (!Object.keys(config.versions.current.downloads).length) {
+ return "";
+ }
+ const downloadsNameDisplay = {
+ pdf: "PDF",
+ epub: "Epub",
+ htmlzip: "HTML",
+ };
+
+ const downloadsHTML = `
+
+ Downloads
+ ${Object.entries(config.versions.current.downloads)
+ .map(
+ ([name, url]) => `
+
+ ${downloadsNameDisplay[name]}
+
+ `,
+ )
+ .join("\n")}
+
+ `;
+ return downloadsHTML;
+ }
+
+ document.addEventListener("readthedocs-addons-data-ready", function (event) {
+ const config = event.detail.data();
+
+ const flyout = `
+
+
+ Read the Docs
+ v: ${config.versions.current.slug}
+
+
+
+
+ ${renderLanguages(config)}
+ ${renderVersions(config)}
+ ${renderDownloads(config)}
+
+ On Read the Docs
+
+ Project Home
+
+
+ Builds
+
+
+ Downloads
+
+
+
+ Search
+
+
+
+
+
+
+ Hosted by Read the Docs
+
+
+
+ `;
+
+ // Inject the generated flyout into the body HTML element.
+ document.body.insertAdjacentHTML("beforeend", flyout);
+
+ // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout.
+ document
+ .querySelector("#flyout-search-form")
+ .addEventListener("focusin", () => {
+ const event = new CustomEvent("readthedocs-search-show");
+ document.dispatchEvent(event);
+ });
+ })
+}
+
+if (themeLanguageSelector || themeVersionSelector) {
+ function onSelectorSwitch(event) {
+ const option = event.target.selectedIndex;
+ const item = event.target.options[option];
+ window.location.href = item.dataset.url;
+ }
+
+ document.addEventListener("readthedocs-addons-data-ready", function (event) {
+ const config = event.detail.data();
+
+ const versionSwitch = document.querySelector(
+ "div.switch-menus > div.version-switch",
+ );
+ if (themeVersionSelector) {
+ let versions = config.versions.active;
+ if (config.versions.current.hidden || config.versions.current.type === "external") {
+ versions.unshift(config.versions.current);
+ }
+ const versionSelect = `
+
+ ${versions
+ .map(
+ (version) => `
+
+ ${version.slug}
+ `,
+ )
+ .join("\n")}
+
+ `;
+
+ versionSwitch.innerHTML = versionSelect;
+ versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch);
+ }
+
+ const languageSwitch = document.querySelector(
+ "div.switch-menus > div.language-switch",
+ );
+
+ if (themeLanguageSelector) {
+ if (config.projects.translations.length) {
+ // Add the current language to the options on the selector
+ let languages = config.projects.translations.concat(
+ config.projects.current,
+ );
+ languages = languages.sort((a, b) =>
+ a.language.name.localeCompare(b.language.name),
+ );
+
+ const languageSelect = `
+
+ ${languages
+ .map(
+ (language) => `
+
+ ${language.language.name}
+ `,
+ )
+ .join("\n")}
+
+ `;
+
+ languageSwitch.innerHTML = languageSelect;
+ languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch);
+ }
+ else {
+ languageSwitch.remove();
+ }
+ }
+ });
+}
+
+document.addEventListener("readthedocs-addons-data-ready", function (event) {
+ // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav.
+ document
+ .querySelector("[role='search'] input")
+ .addEventListener("focusin", () => {
+ const event = new CustomEvent("readthedocs-search-show");
+ document.dispatchEvent(event);
+ });
+});
\ No newline at end of file
diff --git a/branch/main/singlehtml/_static/language_data.js b/branch/main/singlehtml/_static/language_data.js
new file mode 100644
index 0000000..367b8ed
--- /dev/null
+++ b/branch/main/singlehtml/_static/language_data.js
@@ -0,0 +1,199 @@
+/*
+ * language_data.js
+ * ~~~~~~~~~~~~~~~~
+ *
+ * This script contains the language-specific data used by searchtools.js,
+ * namely the list of stopwords, stemmer, scorer and splitter.
+ *
+ * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"];
+
+
+/* Non-minified version is copied as a separate JS file, if available */
+
+/**
+ * Porter Stemmer
+ */
+var Stemmer = function() {
+
+ var step2list = {
+ ational: 'ate',
+ tional: 'tion',
+ enci: 'ence',
+ anci: 'ance',
+ izer: 'ize',
+ bli: 'ble',
+ alli: 'al',
+ entli: 'ent',
+ eli: 'e',
+ ousli: 'ous',
+ ization: 'ize',
+ ation: 'ate',
+ ator: 'ate',
+ alism: 'al',
+ iveness: 'ive',
+ fulness: 'ful',
+ ousness: 'ous',
+ aliti: 'al',
+ iviti: 'ive',
+ biliti: 'ble',
+ logi: 'log'
+ };
+
+ var step3list = {
+ icate: 'ic',
+ ative: '',
+ alize: 'al',
+ iciti: 'ic',
+ ical: 'ic',
+ ful: '',
+ ness: ''
+ };
+
+ var c = "[^aeiou]"; // consonant
+ var v = "[aeiouy]"; // vowel
+ var C = c + "[^aeiouy]*"; // consonant sequence
+ var V = v + "[aeiou]*"; // vowel sequence
+
+ var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
+ var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
+ var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
+ var s_v = "^(" + C + ")?" + v; // vowel in stem
+
+ this.stemWord = function (w) {
+ var stem;
+ var suffix;
+ var firstch;
+ var origword = w;
+
+ if (w.length < 3)
+ return w;
+
+ var re;
+ var re2;
+ var re3;
+ var re4;
+
+ firstch = w.substr(0,1);
+ if (firstch == "y")
+ w = firstch.toUpperCase() + w.substr(1);
+
+ // Step 1a
+ re = /^(.+?)(ss|i)es$/;
+ re2 = /^(.+?)([^s])s$/;
+
+ if (re.test(w))
+ w = w.replace(re,"$1$2");
+ else if (re2.test(w))
+ w = w.replace(re2,"$1$2");
+
+ // Step 1b
+ re = /^(.+?)eed$/;
+ re2 = /^(.+?)(ed|ing)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ re = new RegExp(mgr0);
+ if (re.test(fp[1])) {
+ re = /.$/;
+ w = w.replace(re,"");
+ }
+ }
+ else if (re2.test(w)) {
+ var fp = re2.exec(w);
+ stem = fp[1];
+ re2 = new RegExp(s_v);
+ if (re2.test(stem)) {
+ w = stem;
+ re2 = /(at|bl|iz)$/;
+ re3 = new RegExp("([^aeiouylsz])\\1$");
+ re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
+ if (re2.test(w))
+ w = w + "e";
+ else if (re3.test(w)) {
+ re = /.$/;
+ w = w.replace(re,"");
+ }
+ else if (re4.test(w))
+ w = w + "e";
+ }
+ }
+
+ // Step 1c
+ re = /^(.+?)y$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = new RegExp(s_v);
+ if (re.test(stem))
+ w = stem + "i";
+ }
+
+ // Step 2
+ re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ suffix = fp[2];
+ re = new RegExp(mgr0);
+ if (re.test(stem))
+ w = stem + step2list[suffix];
+ }
+
+ // Step 3
+ re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ suffix = fp[2];
+ re = new RegExp(mgr0);
+ if (re.test(stem))
+ w = stem + step3list[suffix];
+ }
+
+ // Step 4
+ re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
+ re2 = /^(.+?)(s|t)(ion)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = new RegExp(mgr1);
+ if (re.test(stem))
+ w = stem;
+ }
+ else if (re2.test(w)) {
+ var fp = re2.exec(w);
+ stem = fp[1] + fp[2];
+ re2 = new RegExp(mgr1);
+ if (re2.test(stem))
+ w = stem;
+ }
+
+ // Step 5
+ re = /^(.+?)e$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = new RegExp(mgr1);
+ re2 = new RegExp(meq1);
+ re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
+ if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
+ w = stem;
+ }
+ re = /ll$/;
+ re2 = new RegExp(mgr1);
+ if (re.test(w) && re2.test(w)) {
+ re = /.$/;
+ w = w.replace(re,"");
+ }
+
+ // and turn initial Y back to y
+ if (firstch == "y")
+ w = firstch.toLowerCase() + w.substr(1);
+ return w;
+ }
+}
+
diff --git a/branch/main/singlehtml/_static/minipres.js b/branch/main/singlehtml/_static/minipres.js
new file mode 100644
index 0000000..ad11c87
--- /dev/null
+++ b/branch/main/singlehtml/_static/minipres.js
@@ -0,0 +1,223 @@
+// Add goTo method to elements
+// http://stackoverflow.com/questions/4801655/how-to-go-to-a-specific-element-on-page
+(function($) {
+ $.fn.goTo = function() {
+ $('html, body').animate({
+ scrollTop: $(this).offset().top //+ 'px'
+ }, 'fast');
+ return this; // for chaining...
+ }
+})(jQuery);
+
+// NO good way to do this!. Copy a hack from here
+// https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
+// https://stackoverflow.com/a/2880929
+var urlParams;
+(window.onpopstate = function () {
+ var match,
+ pl = /\+/g, // Regex for replacing addition symbol with a space
+ search = /([^&=]+)=?([^&]*)/g,
+ decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
+ query = window.location.search.substring(1);
+ urlParams = {};
+ while (match = search.exec(query))
+ urlParams[decode(match[1])] = decode(match[2]);
+})();
+
+// Select heading levels
+var maxHeading = urlParams['h']
+if (maxHeading === undefined) maxHeading = 2
+var headingLevels = [];
+for (h=2 ; h
(sections.length-1) ) {
+ // if we would scroll past bottom, or above top, do nothing
+ return;
+ }
+
+ console.log('xxxxxx');
+ var targetSection = sections[targetPos];
+ console.log(targetSection, typeof(targetSection));
+
+ // Return targetSection top and height
+ var secProperties = section_top_and_height(targetSection);
+ var top = secProperties['top'];
+ var height = secProperties['height']
+ var win_height = window.innerHeight;
+ //console.info(top, height, win_height)
+
+ var scroll_to = 0;
+ if (height >= win_height || height == 0) {
+ scroll_to = top;
+ } else {
+ scroll_to = top - (win_height-height)/3.;
+ }
+ //console.info(top, height, win_height, scroll_to)
+
+ $('html, body').animate({
+ scrollTop: scroll_to //+ 'px'
+ }, 'fast');
+
+}
+
+
+function minipres() {
+ /* Enable the minipres mode:
+ - call the hide() function
+ - set up the scrolling listener
+ */
+ document.addEventListener('keydown', function (event) {
+ switch(event.which) {
+ case 37: // left
+ switch_slide(-1);
+ event.preventDefault();
+ return false;
+ break;
+ //case 38: // up
+ case 39: // right
+ switch_slide(+1);
+ event.preventDefault();
+ return false;
+ break;
+ //case 40: // down
+ default:
+ return; // exit this handler for other keys
+ }
+ }, true)
+
+ hide()
+
+ // Increase space between sections
+ //$("div .section").css('margin-bottom', '50%');
+ $(sectionSelector).css('margin-top', '50%');
+
+ // Reduce size/color of other sections
+ if (hiddenSectionSelector.length > 0) {
+ var hideNodes = $(hiddenSectionSelector);
+ console.log(typeof hideNodes, hideNodes);
+ for (node in hideNodes) {
+ console.log("a", typeof node, node);
+ node = hideNodes[node]; // what's right way to iterate values?
+ console.log("b", typeof node, node);
+ if (node.parentNode && node.parentNode.className == "section") {
+ node = node.parentNode;
+ console.log("c", typeof node, node);
+ //node.css['transform'] = 'scale(.5)';
+ //node.css['transform-origin'] = 'top center';
+ $(node).css('color', 'lightgrey');
+ //$(node).css('font-size', '20%');
+ //$(node).css('visibility', 'collapse');
+ //ntahousnatouhasno;
+ }
+ }
+ }
+}
+
+function hide() {
+ /* Hide all non-essential elements on the page
+ */
+
+ // This is for sphinx_rst_theme and readthedocs
+ $(".wy-nav-side").remove();
+ $(".wy-nav-content-wrap").css('margin-left', 0);
+ $('.rst-versions').remove(); // readthedocs version selector
+
+ // Add other formats here.
+}
+
+
+var slideshow = minipres;
+
+if (window.location.search.match(/[?&](minipres|slideshow|pres)([=&]|$)/) ) {
+ //minipres()
+ window.addEventListener("load", minipres);
+} else if (window.location.search.match(/[?&](plain)([=&]|$)/) ) {
+ window.addEventListener("load", hide);
+}
diff --git a/branch/main/singlehtml/_static/minus.png b/branch/main/singlehtml/_static/minus.png
new file mode 100644
index 0000000..d96755f
Binary files /dev/null and b/branch/main/singlehtml/_static/minus.png differ
diff --git a/branch/main/singlehtml/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css b/branch/main/singlehtml/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css
new file mode 100644
index 0000000..3356631
--- /dev/null
+++ b/branch/main/singlehtml/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css
@@ -0,0 +1,2342 @@
+/* Variables */
+:root {
+ --mystnb-source-bg-color: #f7f7f7;
+ --mystnb-stdout-bg-color: #fcfcfc;
+ --mystnb-stderr-bg-color: #fdd;
+ --mystnb-traceback-bg-color: #fcfcfc;
+ --mystnb-source-border-color: #ccc;
+ --mystnb-source-margin-color: green;
+ --mystnb-stdout-border-color: #f7f7f7;
+ --mystnb-stderr-border-color: #f7f7f7;
+ --mystnb-traceback-border-color: #ffd6d6;
+ --mystnb-hide-prompt-opacity: 70%;
+ --mystnb-source-border-radius: .4em;
+ --mystnb-source-border-width: 1px;
+}
+
+/* Whole cell */
+div.container.cell {
+ padding-left: 0;
+ margin-bottom: 1em;
+}
+
+/* Removing all background formatting so we can control at the div level */
+.cell_input div.highlight,
+.cell_output pre,
+.cell_input pre,
+.cell_output .output {
+ border: none;
+ box-shadow: none;
+}
+
+.cell_output .output pre,
+.cell_input pre {
+ margin: 0px;
+}
+
+/* Input cells */
+div.cell div.cell_input,
+div.cell details.above-input>summary {
+ padding-left: 0em;
+ padding-right: 0em;
+ border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid;
+ background-color: var(--mystnb-source-bg-color);
+ border-left-color: var(--mystnb-source-margin-color);
+ border-left-width: medium;
+ border-radius: var(--mystnb-source-border-radius);
+}
+
+div.cell_input>div,
+div.cell_output div.output>div.highlight {
+ margin: 0em !important;
+ border: none !important;
+}
+
+/* All cell outputs */
+.cell_output {
+ padding-left: 1em;
+ padding-right: 0em;
+ margin-top: 1em;
+}
+
+/* Text outputs from cells */
+.cell_output .output.text_plain,
+.cell_output .output.traceback,
+.cell_output .output.stream,
+.cell_output .output.stderr {
+ margin-top: 1em;
+ margin-bottom: 0em;
+ box-shadow: none;
+}
+
+.cell_output .output.text_plain,
+.cell_output .output.stream {
+ background: var(--mystnb-stdout-bg-color);
+ border: 1px solid var(--mystnb-stdout-border-color);
+}
+
+.cell_output .output.stderr {
+ background: var(--mystnb-stderr-bg-color);
+ border: 1px solid var(--mystnb-stderr-border-color);
+}
+
+.cell_output .output.traceback {
+ background: var(--mystnb-traceback-bg-color);
+ border: 1px solid var(--mystnb-traceback-border-color);
+}
+
+/* Collapsible cell content */
+div.cell details.above-input div.cell_input {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-top: var(--mystnb-source-border-width) var(--mystnb-source-border-color) dashed;
+}
+
+div.cell div.cell_input.above-output-prompt {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+div.cell details.above-input>summary {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ border-bottom: var(--mystnb-source-border-width) var(--mystnb-source-border-color) dashed;
+ padding-left: 1em;
+ margin-bottom: 0;
+}
+
+div.cell details.above-output>summary {
+ background-color: var(--mystnb-source-bg-color);
+ padding-left: 1em;
+ padding-right: 0em;
+ border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid;
+ border-radius: var(--mystnb-source-border-radius);
+ border-left-color: var(--mystnb-source-margin-color);
+ border-left-width: medium;
+}
+
+div.cell details.below-input>summary {
+ background-color: var(--mystnb-source-bg-color);
+ padding-left: 1em;
+ padding-right: 0em;
+ border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid;
+ border-top: none;
+ border-bottom-left-radius: var(--mystnb-source-border-radius);
+ border-bottom-right-radius: var(--mystnb-source-border-radius);
+ border-left-color: var(--mystnb-source-margin-color);
+ border-left-width: medium;
+}
+
+div.cell details.hide>summary>span {
+ opacity: var(--mystnb-hide-prompt-opacity);
+}
+
+div.cell details.hide[open]>summary>span.collapsed {
+ display: none;
+}
+
+div.cell details.hide:not([open])>summary>span.expanded {
+ display: none;
+}
+
+@keyframes collapsed-fade-in {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
+div.cell details.hide[open]>summary~* {
+ -moz-animation: collapsed-fade-in 0.3s ease-in-out;
+ -webkit-animation: collapsed-fade-in 0.3s ease-in-out;
+ animation: collapsed-fade-in 0.3s ease-in-out;
+}
+
+/* Math align to the left */
+.cell_output .MathJax_Display {
+ text-align: left !important;
+}
+
+/* Pandas tables. Pulled from the Jupyter / nbsphinx CSS */
+div.cell_output table {
+ border: none;
+ border-collapse: collapse;
+ border-spacing: 0;
+ color: black;
+ font-size: 1em;
+ table-layout: fixed;
+}
+
+div.cell_output thead {
+ border-bottom: 1px solid black;
+ vertical-align: bottom;
+}
+
+div.cell_output tr,
+div.cell_output th,
+div.cell_output td {
+ text-align: right;
+ vertical-align: middle;
+ padding: 0.5em 0.5em;
+ line-height: normal;
+ white-space: normal;
+ max-width: none;
+ border: none;
+}
+
+div.cell_output th {
+ font-weight: bold;
+}
+
+div.cell_output tbody tr:nth-child(odd) {
+ background: #f5f5f5;
+}
+
+div.cell_output tbody tr:hover {
+ background: rgba(66, 165, 245, 0.2);
+}
+
+/** source code line numbers **/
+span.linenos {
+ opacity: 0.5;
+}
+
+/* Inline text from `paste` operation */
+
+span.pasted-text {
+ font-weight: bold;
+}
+
+span.pasted-inline img {
+ max-height: 2em;
+}
+
+tbody span.pasted-inline img {
+ max-height: none;
+}
+
+/* Font colors for translated ANSI escape sequences
+Color values are copied from Jupyter Notebook
+https://github.com/jupyter/notebook/blob/52581f8eda9b319eb0390ac77fe5903c38f81e3e/notebook/static/notebook/less/ansicolors.less#L14-L21
+Background colors from
+https://nbsphinx.readthedocs.io/en/latest/code-cells.html#ANSI-Colors
+*/
+div.highlight .-Color-Bold {
+ font-weight: bold;
+}
+
+div.highlight .-Color[class*=-Black] {
+ color: #3E424D
+}
+
+div.highlight .-Color[class*=-Red] {
+ color: #E75C58
+}
+
+div.highlight .-Color[class*=-Green] {
+ color: #00A250
+}
+
+div.highlight .-Color[class*=-Yellow] {
+ color: #DDB62B
+}
+
+div.highlight .-Color[class*=-Blue] {
+ color: #208FFB
+}
+
+div.highlight .-Color[class*=-Magenta] {
+ color: #D160C4
+}
+
+div.highlight .-Color[class*=-Cyan] {
+ color: #60C6C8
+}
+
+div.highlight .-Color[class*=-White] {
+ color: #C5C1B4
+}
+
+div.highlight .-Color[class*=-BGBlack] {
+ background-color: #3E424D
+}
+
+div.highlight .-Color[class*=-BGRed] {
+ background-color: #E75C58
+}
+
+div.highlight .-Color[class*=-BGGreen] {
+ background-color: #00A250
+}
+
+div.highlight .-Color[class*=-BGYellow] {
+ background-color: #DDB62B
+}
+
+div.highlight .-Color[class*=-BGBlue] {
+ background-color: #208FFB
+}
+
+div.highlight .-Color[class*=-BGMagenta] {
+ background-color: #D160C4
+}
+
+div.highlight .-Color[class*=-BGCyan] {
+ background-color: #60C6C8
+}
+
+div.highlight .-Color[class*=-BGWhite] {
+ background-color: #C5C1B4
+}
+
+/* Font colors for 8-bit ANSI */
+
+div.highlight .-Color[class*=-C0] {
+ color: #000000
+}
+
+div.highlight .-Color[class*=-BGC0] {
+ background-color: #000000
+}
+
+div.highlight .-Color[class*=-C1] {
+ color: #800000
+}
+
+div.highlight .-Color[class*=-BGC1] {
+ background-color: #800000
+}
+
+div.highlight .-Color[class*=-C2] {
+ color: #008000
+}
+
+div.highlight .-Color[class*=-BGC2] {
+ background-color: #008000
+}
+
+div.highlight .-Color[class*=-C3] {
+ color: #808000
+}
+
+div.highlight .-Color[class*=-BGC3] {
+ background-color: #808000
+}
+
+div.highlight .-Color[class*=-C4] {
+ color: #000080
+}
+
+div.highlight .-Color[class*=-BGC4] {
+ background-color: #000080
+}
+
+div.highlight .-Color[class*=-C5] {
+ color: #800080
+}
+
+div.highlight .-Color[class*=-BGC5] {
+ background-color: #800080
+}
+
+div.highlight .-Color[class*=-C6] {
+ color: #008080
+}
+
+div.highlight .-Color[class*=-BGC6] {
+ background-color: #008080
+}
+
+div.highlight .-Color[class*=-C7] {
+ color: #C0C0C0
+}
+
+div.highlight .-Color[class*=-BGC7] {
+ background-color: #C0C0C0
+}
+
+div.highlight .-Color[class*=-C8] {
+ color: #808080
+}
+
+div.highlight .-Color[class*=-BGC8] {
+ background-color: #808080
+}
+
+div.highlight .-Color[class*=-C9] {
+ color: #FF0000
+}
+
+div.highlight .-Color[class*=-BGC9] {
+ background-color: #FF0000
+}
+
+div.highlight .-Color[class*=-C10] {
+ color: #00FF00
+}
+
+div.highlight .-Color[class*=-BGC10] {
+ background-color: #00FF00
+}
+
+div.highlight .-Color[class*=-C11] {
+ color: #FFFF00
+}
+
+div.highlight .-Color[class*=-BGC11] {
+ background-color: #FFFF00
+}
+
+div.highlight .-Color[class*=-C12] {
+ color: #0000FF
+}
+
+div.highlight .-Color[class*=-BGC12] {
+ background-color: #0000FF
+}
+
+div.highlight .-Color[class*=-C13] {
+ color: #FF00FF
+}
+
+div.highlight .-Color[class*=-BGC13] {
+ background-color: #FF00FF
+}
+
+div.highlight .-Color[class*=-C14] {
+ color: #00FFFF
+}
+
+div.highlight .-Color[class*=-BGC14] {
+ background-color: #00FFFF
+}
+
+div.highlight .-Color[class*=-C15] {
+ color: #FFFFFF
+}
+
+div.highlight .-Color[class*=-BGC15] {
+ background-color: #FFFFFF
+}
+
+div.highlight .-Color[class*=-C16] {
+ color: #000000
+}
+
+div.highlight .-Color[class*=-BGC16] {
+ background-color: #000000
+}
+
+div.highlight .-Color[class*=-C17] {
+ color: #00005F
+}
+
+div.highlight .-Color[class*=-BGC17] {
+ background-color: #00005F
+}
+
+div.highlight .-Color[class*=-C18] {
+ color: #000087
+}
+
+div.highlight .-Color[class*=-BGC18] {
+ background-color: #000087
+}
+
+div.highlight .-Color[class*=-C19] {
+ color: #0000AF
+}
+
+div.highlight .-Color[class*=-BGC19] {
+ background-color: #0000AF
+}
+
+div.highlight .-Color[class*=-C20] {
+ color: #0000D7
+}
+
+div.highlight .-Color[class*=-BGC20] {
+ background-color: #0000D7
+}
+
+div.highlight .-Color[class*=-C21] {
+ color: #0000FF
+}
+
+div.highlight .-Color[class*=-BGC21] {
+ background-color: #0000FF
+}
+
+div.highlight .-Color[class*=-C22] {
+ color: #005F00
+}
+
+div.highlight .-Color[class*=-BGC22] {
+ background-color: #005F00
+}
+
+div.highlight .-Color[class*=-C23] {
+ color: #005F5F
+}
+
+div.highlight .-Color[class*=-BGC23] {
+ background-color: #005F5F
+}
+
+div.highlight .-Color[class*=-C24] {
+ color: #005F87
+}
+
+div.highlight .-Color[class*=-BGC24] {
+ background-color: #005F87
+}
+
+div.highlight .-Color[class*=-C25] {
+ color: #005FAF
+}
+
+div.highlight .-Color[class*=-BGC25] {
+ background-color: #005FAF
+}
+
+div.highlight .-Color[class*=-C26] {
+ color: #005FD7
+}
+
+div.highlight .-Color[class*=-BGC26] {
+ background-color: #005FD7
+}
+
+div.highlight .-Color[class*=-C27] {
+ color: #005FFF
+}
+
+div.highlight .-Color[class*=-BGC27] {
+ background-color: #005FFF
+}
+
+div.highlight .-Color[class*=-C28] {
+ color: #008700
+}
+
+div.highlight .-Color[class*=-BGC28] {
+ background-color: #008700
+}
+
+div.highlight .-Color[class*=-C29] {
+ color: #00875F
+}
+
+div.highlight .-Color[class*=-BGC29] {
+ background-color: #00875F
+}
+
+div.highlight .-Color[class*=-C30] {
+ color: #008787
+}
+
+div.highlight .-Color[class*=-BGC30] {
+ background-color: #008787
+}
+
+div.highlight .-Color[class*=-C31] {
+ color: #0087AF
+}
+
+div.highlight .-Color[class*=-BGC31] {
+ background-color: #0087AF
+}
+
+div.highlight .-Color[class*=-C32] {
+ color: #0087D7
+}
+
+div.highlight .-Color[class*=-BGC32] {
+ background-color: #0087D7
+}
+
+div.highlight .-Color[class*=-C33] {
+ color: #0087FF
+}
+
+div.highlight .-Color[class*=-BGC33] {
+ background-color: #0087FF
+}
+
+div.highlight .-Color[class*=-C34] {
+ color: #00AF00
+}
+
+div.highlight .-Color[class*=-BGC34] {
+ background-color: #00AF00
+}
+
+div.highlight .-Color[class*=-C35] {
+ color: #00AF5F
+}
+
+div.highlight .-Color[class*=-BGC35] {
+ background-color: #00AF5F
+}
+
+div.highlight .-Color[class*=-C36] {
+ color: #00AF87
+}
+
+div.highlight .-Color[class*=-BGC36] {
+ background-color: #00AF87
+}
+
+div.highlight .-Color[class*=-C37] {
+ color: #00AFAF
+}
+
+div.highlight .-Color[class*=-BGC37] {
+ background-color: #00AFAF
+}
+
+div.highlight .-Color[class*=-C38] {
+ color: #00AFD7
+}
+
+div.highlight .-Color[class*=-BGC38] {
+ background-color: #00AFD7
+}
+
+div.highlight .-Color[class*=-C39] {
+ color: #00AFFF
+}
+
+div.highlight .-Color[class*=-BGC39] {
+ background-color: #00AFFF
+}
+
+div.highlight .-Color[class*=-C40] {
+ color: #00D700
+}
+
+div.highlight .-Color[class*=-BGC40] {
+ background-color: #00D700
+}
+
+div.highlight .-Color[class*=-C41] {
+ color: #00D75F
+}
+
+div.highlight .-Color[class*=-BGC41] {
+ background-color: #00D75F
+}
+
+div.highlight .-Color[class*=-C42] {
+ color: #00D787
+}
+
+div.highlight .-Color[class*=-BGC42] {
+ background-color: #00D787
+}
+
+div.highlight .-Color[class*=-C43] {
+ color: #00D7AF
+}
+
+div.highlight .-Color[class*=-BGC43] {
+ background-color: #00D7AF
+}
+
+div.highlight .-Color[class*=-C44] {
+ color: #00D7D7
+}
+
+div.highlight .-Color[class*=-BGC44] {
+ background-color: #00D7D7
+}
+
+div.highlight .-Color[class*=-C45] {
+ color: #00D7FF
+}
+
+div.highlight .-Color[class*=-BGC45] {
+ background-color: #00D7FF
+}
+
+div.highlight .-Color[class*=-C46] {
+ color: #00FF00
+}
+
+div.highlight .-Color[class*=-BGC46] {
+ background-color: #00FF00
+}
+
+div.highlight .-Color[class*=-C47] {
+ color: #00FF5F
+}
+
+div.highlight .-Color[class*=-BGC47] {
+ background-color: #00FF5F
+}
+
+div.highlight .-Color[class*=-C48] {
+ color: #00FF87
+}
+
+div.highlight .-Color[class*=-BGC48] {
+ background-color: #00FF87
+}
+
+div.highlight .-Color[class*=-C49] {
+ color: #00FFAF
+}
+
+div.highlight .-Color[class*=-BGC49] {
+ background-color: #00FFAF
+}
+
+div.highlight .-Color[class*=-C50] {
+ color: #00FFD7
+}
+
+div.highlight .-Color[class*=-BGC50] {
+ background-color: #00FFD7
+}
+
+div.highlight .-Color[class*=-C51] {
+ color: #00FFFF
+}
+
+div.highlight .-Color[class*=-BGC51] {
+ background-color: #00FFFF
+}
+
+div.highlight .-Color[class*=-C52] {
+ color: #5F0000
+}
+
+div.highlight .-Color[class*=-BGC52] {
+ background-color: #5F0000
+}
+
+div.highlight .-Color[class*=-C53] {
+ color: #5F005F
+}
+
+div.highlight .-Color[class*=-BGC53] {
+ background-color: #5F005F
+}
+
+div.highlight .-Color[class*=-C54] {
+ color: #5F0087
+}
+
+div.highlight .-Color[class*=-BGC54] {
+ background-color: #5F0087
+}
+
+div.highlight .-Color[class*=-C55] {
+ color: #5F00AF
+}
+
+div.highlight .-Color[class*=-BGC55] {
+ background-color: #5F00AF
+}
+
+div.highlight .-Color[class*=-C56] {
+ color: #5F00D7
+}
+
+div.highlight .-Color[class*=-BGC56] {
+ background-color: #5F00D7
+}
+
+div.highlight .-Color[class*=-C57] {
+ color: #5F00FF
+}
+
+div.highlight .-Color[class*=-BGC57] {
+ background-color: #5F00FF
+}
+
+div.highlight .-Color[class*=-C58] {
+ color: #5F5F00
+}
+
+div.highlight .-Color[class*=-BGC58] {
+ background-color: #5F5F00
+}
+
+div.highlight .-Color[class*=-C59] {
+ color: #5F5F5F
+}
+
+div.highlight .-Color[class*=-BGC59] {
+ background-color: #5F5F5F
+}
+
+div.highlight .-Color[class*=-C60] {
+ color: #5F5F87
+}
+
+div.highlight .-Color[class*=-BGC60] {
+ background-color: #5F5F87
+}
+
+div.highlight .-Color[class*=-C61] {
+ color: #5F5FAF
+}
+
+div.highlight .-Color[class*=-BGC61] {
+ background-color: #5F5FAF
+}
+
+div.highlight .-Color[class*=-C62] {
+ color: #5F5FD7
+}
+
+div.highlight .-Color[class*=-BGC62] {
+ background-color: #5F5FD7
+}
+
+div.highlight .-Color[class*=-C63] {
+ color: #5F5FFF
+}
+
+div.highlight .-Color[class*=-BGC63] {
+ background-color: #5F5FFF
+}
+
+div.highlight .-Color[class*=-C64] {
+ color: #5F8700
+}
+
+div.highlight .-Color[class*=-BGC64] {
+ background-color: #5F8700
+}
+
+div.highlight .-Color[class*=-C65] {
+ color: #5F875F
+}
+
+div.highlight .-Color[class*=-BGC65] {
+ background-color: #5F875F
+}
+
+div.highlight .-Color[class*=-C66] {
+ color: #5F8787
+}
+
+div.highlight .-Color[class*=-BGC66] {
+ background-color: #5F8787
+}
+
+div.highlight .-Color[class*=-C67] {
+ color: #5F87AF
+}
+
+div.highlight .-Color[class*=-BGC67] {
+ background-color: #5F87AF
+}
+
+div.highlight .-Color[class*=-C68] {
+ color: #5F87D7
+}
+
+div.highlight .-Color[class*=-BGC68] {
+ background-color: #5F87D7
+}
+
+div.highlight .-Color[class*=-C69] {
+ color: #5F87FF
+}
+
+div.highlight .-Color[class*=-BGC69] {
+ background-color: #5F87FF
+}
+
+div.highlight .-Color[class*=-C70] {
+ color: #5FAF00
+}
+
+div.highlight .-Color[class*=-BGC70] {
+ background-color: #5FAF00
+}
+
+div.highlight .-Color[class*=-C71] {
+ color: #5FAF5F
+}
+
+div.highlight .-Color[class*=-BGC71] {
+ background-color: #5FAF5F
+}
+
+div.highlight .-Color[class*=-C72] {
+ color: #5FAF87
+}
+
+div.highlight .-Color[class*=-BGC72] {
+ background-color: #5FAF87
+}
+
+div.highlight .-Color[class*=-C73] {
+ color: #5FAFAF
+}
+
+div.highlight .-Color[class*=-BGC73] {
+ background-color: #5FAFAF
+}
+
+div.highlight .-Color[class*=-C74] {
+ color: #5FAFD7
+}
+
+div.highlight .-Color[class*=-BGC74] {
+ background-color: #5FAFD7
+}
+
+div.highlight .-Color[class*=-C75] {
+ color: #5FAFFF
+}
+
+div.highlight .-Color[class*=-BGC75] {
+ background-color: #5FAFFF
+}
+
+div.highlight .-Color[class*=-C76] {
+ color: #5FD700
+}
+
+div.highlight .-Color[class*=-BGC76] {
+ background-color: #5FD700
+}
+
+div.highlight .-Color[class*=-C77] {
+ color: #5FD75F
+}
+
+div.highlight .-Color[class*=-BGC77] {
+ background-color: #5FD75F
+}
+
+div.highlight .-Color[class*=-C78] {
+ color: #5FD787
+}
+
+div.highlight .-Color[class*=-BGC78] {
+ background-color: #5FD787
+}
+
+div.highlight .-Color[class*=-C79] {
+ color: #5FD7AF
+}
+
+div.highlight .-Color[class*=-BGC79] {
+ background-color: #5FD7AF
+}
+
+div.highlight .-Color[class*=-C80] {
+ color: #5FD7D7
+}
+
+div.highlight .-Color[class*=-BGC80] {
+ background-color: #5FD7D7
+}
+
+div.highlight .-Color[class*=-C81] {
+ color: #5FD7FF
+}
+
+div.highlight .-Color[class*=-BGC81] {
+ background-color: #5FD7FF
+}
+
+div.highlight .-Color[class*=-C82] {
+ color: #5FFF00
+}
+
+div.highlight .-Color[class*=-BGC82] {
+ background-color: #5FFF00
+}
+
+div.highlight .-Color[class*=-C83] {
+ color: #5FFF5F
+}
+
+div.highlight .-Color[class*=-BGC83] {
+ background-color: #5FFF5F
+}
+
+div.highlight .-Color[class*=-C84] {
+ color: #5FFF87
+}
+
+div.highlight .-Color[class*=-BGC84] {
+ background-color: #5FFF87
+}
+
+div.highlight .-Color[class*=-C85] {
+ color: #5FFFAF
+}
+
+div.highlight .-Color[class*=-BGC85] {
+ background-color: #5FFFAF
+}
+
+div.highlight .-Color[class*=-C86] {
+ color: #5FFFD7
+}
+
+div.highlight .-Color[class*=-BGC86] {
+ background-color: #5FFFD7
+}
+
+div.highlight .-Color[class*=-C87] {
+ color: #5FFFFF
+}
+
+div.highlight .-Color[class*=-BGC87] {
+ background-color: #5FFFFF
+}
+
+div.highlight .-Color[class*=-C88] {
+ color: #870000
+}
+
+div.highlight .-Color[class*=-BGC88] {
+ background-color: #870000
+}
+
+div.highlight .-Color[class*=-C89] {
+ color: #87005F
+}
+
+div.highlight .-Color[class*=-BGC89] {
+ background-color: #87005F
+}
+
+div.highlight .-Color[class*=-C90] {
+ color: #870087
+}
+
+div.highlight .-Color[class*=-BGC90] {
+ background-color: #870087
+}
+
+div.highlight .-Color[class*=-C91] {
+ color: #8700AF
+}
+
+div.highlight .-Color[class*=-BGC91] {
+ background-color: #8700AF
+}
+
+div.highlight .-Color[class*=-C92] {
+ color: #8700D7
+}
+
+div.highlight .-Color[class*=-BGC92] {
+ background-color: #8700D7
+}
+
+div.highlight .-Color[class*=-C93] {
+ color: #8700FF
+}
+
+div.highlight .-Color[class*=-BGC93] {
+ background-color: #8700FF
+}
+
+div.highlight .-Color[class*=-C94] {
+ color: #875F00
+}
+
+div.highlight .-Color[class*=-BGC94] {
+ background-color: #875F00
+}
+
+div.highlight .-Color[class*=-C95] {
+ color: #875F5F
+}
+
+div.highlight .-Color[class*=-BGC95] {
+ background-color: #875F5F
+}
+
+div.highlight .-Color[class*=-C96] {
+ color: #875F87
+}
+
+div.highlight .-Color[class*=-BGC96] {
+ background-color: #875F87
+}
+
+div.highlight .-Color[class*=-C97] {
+ color: #875FAF
+}
+
+div.highlight .-Color[class*=-BGC97] {
+ background-color: #875FAF
+}
+
+div.highlight .-Color[class*=-C98] {
+ color: #875FD7
+}
+
+div.highlight .-Color[class*=-BGC98] {
+ background-color: #875FD7
+}
+
+div.highlight .-Color[class*=-C99] {
+ color: #875FFF
+}
+
+div.highlight .-Color[class*=-BGC99] {
+ background-color: #875FFF
+}
+
+div.highlight .-Color[class*=-C100] {
+ color: #878700
+}
+
+div.highlight .-Color[class*=-BGC100] {
+ background-color: #878700
+}
+
+div.highlight .-Color[class*=-C101] {
+ color: #87875F
+}
+
+div.highlight .-Color[class*=-BGC101] {
+ background-color: #87875F
+}
+
+div.highlight .-Color[class*=-C102] {
+ color: #878787
+}
+
+div.highlight .-Color[class*=-BGC102] {
+ background-color: #878787
+}
+
+div.highlight .-Color[class*=-C103] {
+ color: #8787AF
+}
+
+div.highlight .-Color[class*=-BGC103] {
+ background-color: #8787AF
+}
+
+div.highlight .-Color[class*=-C104] {
+ color: #8787D7
+}
+
+div.highlight .-Color[class*=-BGC104] {
+ background-color: #8787D7
+}
+
+div.highlight .-Color[class*=-C105] {
+ color: #8787FF
+}
+
+div.highlight .-Color[class*=-BGC105] {
+ background-color: #8787FF
+}
+
+div.highlight .-Color[class*=-C106] {
+ color: #87AF00
+}
+
+div.highlight .-Color[class*=-BGC106] {
+ background-color: #87AF00
+}
+
+div.highlight .-Color[class*=-C107] {
+ color: #87AF5F
+}
+
+div.highlight .-Color[class*=-BGC107] {
+ background-color: #87AF5F
+}
+
+div.highlight .-Color[class*=-C108] {
+ color: #87AF87
+}
+
+div.highlight .-Color[class*=-BGC108] {
+ background-color: #87AF87
+}
+
+div.highlight .-Color[class*=-C109] {
+ color: #87AFAF
+}
+
+div.highlight .-Color[class*=-BGC109] {
+ background-color: #87AFAF
+}
+
+div.highlight .-Color[class*=-C110] {
+ color: #87AFD7
+}
+
+div.highlight .-Color[class*=-BGC110] {
+ background-color: #87AFD7
+}
+
+div.highlight .-Color[class*=-C111] {
+ color: #87AFFF
+}
+
+div.highlight .-Color[class*=-BGC111] {
+ background-color: #87AFFF
+}
+
+div.highlight .-Color[class*=-C112] {
+ color: #87D700
+}
+
+div.highlight .-Color[class*=-BGC112] {
+ background-color: #87D700
+}
+
+div.highlight .-Color[class*=-C113] {
+ color: #87D75F
+}
+
+div.highlight .-Color[class*=-BGC113] {
+ background-color: #87D75F
+}
+
+div.highlight .-Color[class*=-C114] {
+ color: #87D787
+}
+
+div.highlight .-Color[class*=-BGC114] {
+ background-color: #87D787
+}
+
+div.highlight .-Color[class*=-C115] {
+ color: #87D7AF
+}
+
+div.highlight .-Color[class*=-BGC115] {
+ background-color: #87D7AF
+}
+
+div.highlight .-Color[class*=-C116] {
+ color: #87D7D7
+}
+
+div.highlight .-Color[class*=-BGC116] {
+ background-color: #87D7D7
+}
+
+div.highlight .-Color[class*=-C117] {
+ color: #87D7FF
+}
+
+div.highlight .-Color[class*=-BGC117] {
+ background-color: #87D7FF
+}
+
+div.highlight .-Color[class*=-C118] {
+ color: #87FF00
+}
+
+div.highlight .-Color[class*=-BGC118] {
+ background-color: #87FF00
+}
+
+div.highlight .-Color[class*=-C119] {
+ color: #87FF5F
+}
+
+div.highlight .-Color[class*=-BGC119] {
+ background-color: #87FF5F
+}
+
+div.highlight .-Color[class*=-C120] {
+ color: #87FF87
+}
+
+div.highlight .-Color[class*=-BGC120] {
+ background-color: #87FF87
+}
+
+div.highlight .-Color[class*=-C121] {
+ color: #87FFAF
+}
+
+div.highlight .-Color[class*=-BGC121] {
+ background-color: #87FFAF
+}
+
+div.highlight .-Color[class*=-C122] {
+ color: #87FFD7
+}
+
+div.highlight .-Color[class*=-BGC122] {
+ background-color: #87FFD7
+}
+
+div.highlight .-Color[class*=-C123] {
+ color: #87FFFF
+}
+
+div.highlight .-Color[class*=-BGC123] {
+ background-color: #87FFFF
+}
+
+div.highlight .-Color[class*=-C124] {
+ color: #AF0000
+}
+
+div.highlight .-Color[class*=-BGC124] {
+ background-color: #AF0000
+}
+
+div.highlight .-Color[class*=-C125] {
+ color: #AF005F
+}
+
+div.highlight .-Color[class*=-BGC125] {
+ background-color: #AF005F
+}
+
+div.highlight .-Color[class*=-C126] {
+ color: #AF0087
+}
+
+div.highlight .-Color[class*=-BGC126] {
+ background-color: #AF0087
+}
+
+div.highlight .-Color[class*=-C127] {
+ color: #AF00AF
+}
+
+div.highlight .-Color[class*=-BGC127] {
+ background-color: #AF00AF
+}
+
+div.highlight .-Color[class*=-C128] {
+ color: #AF00D7
+}
+
+div.highlight .-Color[class*=-BGC128] {
+ background-color: #AF00D7
+}
+
+div.highlight .-Color[class*=-C129] {
+ color: #AF00FF
+}
+
+div.highlight .-Color[class*=-BGC129] {
+ background-color: #AF00FF
+}
+
+div.highlight .-Color[class*=-C130] {
+ color: #AF5F00
+}
+
+div.highlight .-Color[class*=-BGC130] {
+ background-color: #AF5F00
+}
+
+div.highlight .-Color[class*=-C131] {
+ color: #AF5F5F
+}
+
+div.highlight .-Color[class*=-BGC131] {
+ background-color: #AF5F5F
+}
+
+div.highlight .-Color[class*=-C132] {
+ color: #AF5F87
+}
+
+div.highlight .-Color[class*=-BGC132] {
+ background-color: #AF5F87
+}
+
+div.highlight .-Color[class*=-C133] {
+ color: #AF5FAF
+}
+
+div.highlight .-Color[class*=-BGC133] {
+ background-color: #AF5FAF
+}
+
+div.highlight .-Color[class*=-C134] {
+ color: #AF5FD7
+}
+
+div.highlight .-Color[class*=-BGC134] {
+ background-color: #AF5FD7
+}
+
+div.highlight .-Color[class*=-C135] {
+ color: #AF5FFF
+}
+
+div.highlight .-Color[class*=-BGC135] {
+ background-color: #AF5FFF
+}
+
+div.highlight .-Color[class*=-C136] {
+ color: #AF8700
+}
+
+div.highlight .-Color[class*=-BGC136] {
+ background-color: #AF8700
+}
+
+div.highlight .-Color[class*=-C137] {
+ color: #AF875F
+}
+
+div.highlight .-Color[class*=-BGC137] {
+ background-color: #AF875F
+}
+
+div.highlight .-Color[class*=-C138] {
+ color: #AF8787
+}
+
+div.highlight .-Color[class*=-BGC138] {
+ background-color: #AF8787
+}
+
+div.highlight .-Color[class*=-C139] {
+ color: #AF87AF
+}
+
+div.highlight .-Color[class*=-BGC139] {
+ background-color: #AF87AF
+}
+
+div.highlight .-Color[class*=-C140] {
+ color: #AF87D7
+}
+
+div.highlight .-Color[class*=-BGC140] {
+ background-color: #AF87D7
+}
+
+div.highlight .-Color[class*=-C141] {
+ color: #AF87FF
+}
+
+div.highlight .-Color[class*=-BGC141] {
+ background-color: #AF87FF
+}
+
+div.highlight .-Color[class*=-C142] {
+ color: #AFAF00
+}
+
+div.highlight .-Color[class*=-BGC142] {
+ background-color: #AFAF00
+}
+
+div.highlight .-Color[class*=-C143] {
+ color: #AFAF5F
+}
+
+div.highlight .-Color[class*=-BGC143] {
+ background-color: #AFAF5F
+}
+
+div.highlight .-Color[class*=-C144] {
+ color: #AFAF87
+}
+
+div.highlight .-Color[class*=-BGC144] {
+ background-color: #AFAF87
+}
+
+div.highlight .-Color[class*=-C145] {
+ color: #AFAFAF
+}
+
+div.highlight .-Color[class*=-BGC145] {
+ background-color: #AFAFAF
+}
+
+div.highlight .-Color[class*=-C146] {
+ color: #AFAFD7
+}
+
+div.highlight .-Color[class*=-BGC146] {
+ background-color: #AFAFD7
+}
+
+div.highlight .-Color[class*=-C147] {
+ color: #AFAFFF
+}
+
+div.highlight .-Color[class*=-BGC147] {
+ background-color: #AFAFFF
+}
+
+div.highlight .-Color[class*=-C148] {
+ color: #AFD700
+}
+
+div.highlight .-Color[class*=-BGC148] {
+ background-color: #AFD700
+}
+
+div.highlight .-Color[class*=-C149] {
+ color: #AFD75F
+}
+
+div.highlight .-Color[class*=-BGC149] {
+ background-color: #AFD75F
+}
+
+div.highlight .-Color[class*=-C150] {
+ color: #AFD787
+}
+
+div.highlight .-Color[class*=-BGC150] {
+ background-color: #AFD787
+}
+
+div.highlight .-Color[class*=-C151] {
+ color: #AFD7AF
+}
+
+div.highlight .-Color[class*=-BGC151] {
+ background-color: #AFD7AF
+}
+
+div.highlight .-Color[class*=-C152] {
+ color: #AFD7D7
+}
+
+div.highlight .-Color[class*=-BGC152] {
+ background-color: #AFD7D7
+}
+
+div.highlight .-Color[class*=-C153] {
+ color: #AFD7FF
+}
+
+div.highlight .-Color[class*=-BGC153] {
+ background-color: #AFD7FF
+}
+
+div.highlight .-Color[class*=-C154] {
+ color: #AFFF00
+}
+
+div.highlight .-Color[class*=-BGC154] {
+ background-color: #AFFF00
+}
+
+div.highlight .-Color[class*=-C155] {
+ color: #AFFF5F
+}
+
+div.highlight .-Color[class*=-BGC155] {
+ background-color: #AFFF5F
+}
+
+div.highlight .-Color[class*=-C156] {
+ color: #AFFF87
+}
+
+div.highlight .-Color[class*=-BGC156] {
+ background-color: #AFFF87
+}
+
+div.highlight .-Color[class*=-C157] {
+ color: #AFFFAF
+}
+
+div.highlight .-Color[class*=-BGC157] {
+ background-color: #AFFFAF
+}
+
+div.highlight .-Color[class*=-C158] {
+ color: #AFFFD7
+}
+
+div.highlight .-Color[class*=-BGC158] {
+ background-color: #AFFFD7
+}
+
+div.highlight .-Color[class*=-C159] {
+ color: #AFFFFF
+}
+
+div.highlight .-Color[class*=-BGC159] {
+ background-color: #AFFFFF
+}
+
+div.highlight .-Color[class*=-C160] {
+ color: #D70000
+}
+
+div.highlight .-Color[class*=-BGC160] {
+ background-color: #D70000
+}
+
+div.highlight .-Color[class*=-C161] {
+ color: #D7005F
+}
+
+div.highlight .-Color[class*=-BGC161] {
+ background-color: #D7005F
+}
+
+div.highlight .-Color[class*=-C162] {
+ color: #D70087
+}
+
+div.highlight .-Color[class*=-BGC162] {
+ background-color: #D70087
+}
+
+div.highlight .-Color[class*=-C163] {
+ color: #D700AF
+}
+
+div.highlight .-Color[class*=-BGC163] {
+ background-color: #D700AF
+}
+
+div.highlight .-Color[class*=-C164] {
+ color: #D700D7
+}
+
+div.highlight .-Color[class*=-BGC164] {
+ background-color: #D700D7
+}
+
+div.highlight .-Color[class*=-C165] {
+ color: #D700FF
+}
+
+div.highlight .-Color[class*=-BGC165] {
+ background-color: #D700FF
+}
+
+div.highlight .-Color[class*=-C166] {
+ color: #D75F00
+}
+
+div.highlight .-Color[class*=-BGC166] {
+ background-color: #D75F00
+}
+
+div.highlight .-Color[class*=-C167] {
+ color: #D75F5F
+}
+
+div.highlight .-Color[class*=-BGC167] {
+ background-color: #D75F5F
+}
+
+div.highlight .-Color[class*=-C168] {
+ color: #D75F87
+}
+
+div.highlight .-Color[class*=-BGC168] {
+ background-color: #D75F87
+}
+
+div.highlight .-Color[class*=-C169] {
+ color: #D75FAF
+}
+
+div.highlight .-Color[class*=-BGC169] {
+ background-color: #D75FAF
+}
+
+div.highlight .-Color[class*=-C170] {
+ color: #D75FD7
+}
+
+div.highlight .-Color[class*=-BGC170] {
+ background-color: #D75FD7
+}
+
+div.highlight .-Color[class*=-C171] {
+ color: #D75FFF
+}
+
+div.highlight .-Color[class*=-BGC171] {
+ background-color: #D75FFF
+}
+
+div.highlight .-Color[class*=-C172] {
+ color: #D78700
+}
+
+div.highlight .-Color[class*=-BGC172] {
+ background-color: #D78700
+}
+
+div.highlight .-Color[class*=-C173] {
+ color: #D7875F
+}
+
+div.highlight .-Color[class*=-BGC173] {
+ background-color: #D7875F
+}
+
+div.highlight .-Color[class*=-C174] {
+ color: #D78787
+}
+
+div.highlight .-Color[class*=-BGC174] {
+ background-color: #D78787
+}
+
+div.highlight .-Color[class*=-C175] {
+ color: #D787AF
+}
+
+div.highlight .-Color[class*=-BGC175] {
+ background-color: #D787AF
+}
+
+div.highlight .-Color[class*=-C176] {
+ color: #D787D7
+}
+
+div.highlight .-Color[class*=-BGC176] {
+ background-color: #D787D7
+}
+
+div.highlight .-Color[class*=-C177] {
+ color: #D787FF
+}
+
+div.highlight .-Color[class*=-BGC177] {
+ background-color: #D787FF
+}
+
+div.highlight .-Color[class*=-C178] {
+ color: #D7AF00
+}
+
+div.highlight .-Color[class*=-BGC178] {
+ background-color: #D7AF00
+}
+
+div.highlight .-Color[class*=-C179] {
+ color: #D7AF5F
+}
+
+div.highlight .-Color[class*=-BGC179] {
+ background-color: #D7AF5F
+}
+
+div.highlight .-Color[class*=-C180] {
+ color: #D7AF87
+}
+
+div.highlight .-Color[class*=-BGC180] {
+ background-color: #D7AF87
+}
+
+div.highlight .-Color[class*=-C181] {
+ color: #D7AFAF
+}
+
+div.highlight .-Color[class*=-BGC181] {
+ background-color: #D7AFAF
+}
+
+div.highlight .-Color[class*=-C182] {
+ color: #D7AFD7
+}
+
+div.highlight .-Color[class*=-BGC182] {
+ background-color: #D7AFD7
+}
+
+div.highlight .-Color[class*=-C183] {
+ color: #D7AFFF
+}
+
+div.highlight .-Color[class*=-BGC183] {
+ background-color: #D7AFFF
+}
+
+div.highlight .-Color[class*=-C184] {
+ color: #D7D700
+}
+
+div.highlight .-Color[class*=-BGC184] {
+ background-color: #D7D700
+}
+
+div.highlight .-Color[class*=-C185] {
+ color: #D7D75F
+}
+
+div.highlight .-Color[class*=-BGC185] {
+ background-color: #D7D75F
+}
+
+div.highlight .-Color[class*=-C186] {
+ color: #D7D787
+}
+
+div.highlight .-Color[class*=-BGC186] {
+ background-color: #D7D787
+}
+
+div.highlight .-Color[class*=-C187] {
+ color: #D7D7AF
+}
+
+div.highlight .-Color[class*=-BGC187] {
+ background-color: #D7D7AF
+}
+
+div.highlight .-Color[class*=-C188] {
+ color: #D7D7D7
+}
+
+div.highlight .-Color[class*=-BGC188] {
+ background-color: #D7D7D7
+}
+
+div.highlight .-Color[class*=-C189] {
+ color: #D7D7FF
+}
+
+div.highlight .-Color[class*=-BGC189] {
+ background-color: #D7D7FF
+}
+
+div.highlight .-Color[class*=-C190] {
+ color: #D7FF00
+}
+
+div.highlight .-Color[class*=-BGC190] {
+ background-color: #D7FF00
+}
+
+div.highlight .-Color[class*=-C191] {
+ color: #D7FF5F
+}
+
+div.highlight .-Color[class*=-BGC191] {
+ background-color: #D7FF5F
+}
+
+div.highlight .-Color[class*=-C192] {
+ color: #D7FF87
+}
+
+div.highlight .-Color[class*=-BGC192] {
+ background-color: #D7FF87
+}
+
+div.highlight .-Color[class*=-C193] {
+ color: #D7FFAF
+}
+
+div.highlight .-Color[class*=-BGC193] {
+ background-color: #D7FFAF
+}
+
+div.highlight .-Color[class*=-C194] {
+ color: #D7FFD7
+}
+
+div.highlight .-Color[class*=-BGC194] {
+ background-color: #D7FFD7
+}
+
+div.highlight .-Color[class*=-C195] {
+ color: #D7FFFF
+}
+
+div.highlight .-Color[class*=-BGC195] {
+ background-color: #D7FFFF
+}
+
+div.highlight .-Color[class*=-C196] {
+ color: #FF0000
+}
+
+div.highlight .-Color[class*=-BGC196] {
+ background-color: #FF0000
+}
+
+div.highlight .-Color[class*=-C197] {
+ color: #FF005F
+}
+
+div.highlight .-Color[class*=-BGC197] {
+ background-color: #FF005F
+}
+
+div.highlight .-Color[class*=-C198] {
+ color: #FF0087
+}
+
+div.highlight .-Color[class*=-BGC198] {
+ background-color: #FF0087
+}
+
+div.highlight .-Color[class*=-C199] {
+ color: #FF00AF
+}
+
+div.highlight .-Color[class*=-BGC199] {
+ background-color: #FF00AF
+}
+
+div.highlight .-Color[class*=-C200] {
+ color: #FF00D7
+}
+
+div.highlight .-Color[class*=-BGC200] {
+ background-color: #FF00D7
+}
+
+div.highlight .-Color[class*=-C201] {
+ color: #FF00FF
+}
+
+div.highlight .-Color[class*=-BGC201] {
+ background-color: #FF00FF
+}
+
+div.highlight .-Color[class*=-C202] {
+ color: #FF5F00
+}
+
+div.highlight .-Color[class*=-BGC202] {
+ background-color: #FF5F00
+}
+
+div.highlight .-Color[class*=-C203] {
+ color: #FF5F5F
+}
+
+div.highlight .-Color[class*=-BGC203] {
+ background-color: #FF5F5F
+}
+
+div.highlight .-Color[class*=-C204] {
+ color: #FF5F87
+}
+
+div.highlight .-Color[class*=-BGC204] {
+ background-color: #FF5F87
+}
+
+div.highlight .-Color[class*=-C205] {
+ color: #FF5FAF
+}
+
+div.highlight .-Color[class*=-BGC205] {
+ background-color: #FF5FAF
+}
+
+div.highlight .-Color[class*=-C206] {
+ color: #FF5FD7
+}
+
+div.highlight .-Color[class*=-BGC206] {
+ background-color: #FF5FD7
+}
+
+div.highlight .-Color[class*=-C207] {
+ color: #FF5FFF
+}
+
+div.highlight .-Color[class*=-BGC207] {
+ background-color: #FF5FFF
+}
+
+div.highlight .-Color[class*=-C208] {
+ color: #FF8700
+}
+
+div.highlight .-Color[class*=-BGC208] {
+ background-color: #FF8700
+}
+
+div.highlight .-Color[class*=-C209] {
+ color: #FF875F
+}
+
+div.highlight .-Color[class*=-BGC209] {
+ background-color: #FF875F
+}
+
+div.highlight .-Color[class*=-C210] {
+ color: #FF8787
+}
+
+div.highlight .-Color[class*=-BGC210] {
+ background-color: #FF8787
+}
+
+div.highlight .-Color[class*=-C211] {
+ color: #FF87AF
+}
+
+div.highlight .-Color[class*=-BGC211] {
+ background-color: #FF87AF
+}
+
+div.highlight .-Color[class*=-C212] {
+ color: #FF87D7
+}
+
+div.highlight .-Color[class*=-BGC212] {
+ background-color: #FF87D7
+}
+
+div.highlight .-Color[class*=-C213] {
+ color: #FF87FF
+}
+
+div.highlight .-Color[class*=-BGC213] {
+ background-color: #FF87FF
+}
+
+div.highlight .-Color[class*=-C214] {
+ color: #FFAF00
+}
+
+div.highlight .-Color[class*=-BGC214] {
+ background-color: #FFAF00
+}
+
+div.highlight .-Color[class*=-C215] {
+ color: #FFAF5F
+}
+
+div.highlight .-Color[class*=-BGC215] {
+ background-color: #FFAF5F
+}
+
+div.highlight .-Color[class*=-C216] {
+ color: #FFAF87
+}
+
+div.highlight .-Color[class*=-BGC216] {
+ background-color: #FFAF87
+}
+
+div.highlight .-Color[class*=-C217] {
+ color: #FFAFAF
+}
+
+div.highlight .-Color[class*=-BGC217] {
+ background-color: #FFAFAF
+}
+
+div.highlight .-Color[class*=-C218] {
+ color: #FFAFD7
+}
+
+div.highlight .-Color[class*=-BGC218] {
+ background-color: #FFAFD7
+}
+
+div.highlight .-Color[class*=-C219] {
+ color: #FFAFFF
+}
+
+div.highlight .-Color[class*=-BGC219] {
+ background-color: #FFAFFF
+}
+
+div.highlight .-Color[class*=-C220] {
+ color: #FFD700
+}
+
+div.highlight .-Color[class*=-BGC220] {
+ background-color: #FFD700
+}
+
+div.highlight .-Color[class*=-C221] {
+ color: #FFD75F
+}
+
+div.highlight .-Color[class*=-BGC221] {
+ background-color: #FFD75F
+}
+
+div.highlight .-Color[class*=-C222] {
+ color: #FFD787
+}
+
+div.highlight .-Color[class*=-BGC222] {
+ background-color: #FFD787
+}
+
+div.highlight .-Color[class*=-C223] {
+ color: #FFD7AF
+}
+
+div.highlight .-Color[class*=-BGC223] {
+ background-color: #FFD7AF
+}
+
+div.highlight .-Color[class*=-C224] {
+ color: #FFD7D7
+}
+
+div.highlight .-Color[class*=-BGC224] {
+ background-color: #FFD7D7
+}
+
+div.highlight .-Color[class*=-C225] {
+ color: #FFD7FF
+}
+
+div.highlight .-Color[class*=-BGC225] {
+ background-color: #FFD7FF
+}
+
+div.highlight .-Color[class*=-C226] {
+ color: #FFFF00
+}
+
+div.highlight .-Color[class*=-BGC226] {
+ background-color: #FFFF00
+}
+
+div.highlight .-Color[class*=-C227] {
+ color: #FFFF5F
+}
+
+div.highlight .-Color[class*=-BGC227] {
+ background-color: #FFFF5F
+}
+
+div.highlight .-Color[class*=-C228] {
+ color: #FFFF87
+}
+
+div.highlight .-Color[class*=-BGC228] {
+ background-color: #FFFF87
+}
+
+div.highlight .-Color[class*=-C229] {
+ color: #FFFFAF
+}
+
+div.highlight .-Color[class*=-BGC229] {
+ background-color: #FFFFAF
+}
+
+div.highlight .-Color[class*=-C230] {
+ color: #FFFFD7
+}
+
+div.highlight .-Color[class*=-BGC230] {
+ background-color: #FFFFD7
+}
+
+div.highlight .-Color[class*=-C231] {
+ color: #FFFFFF
+}
+
+div.highlight .-Color[class*=-BGC231] {
+ background-color: #FFFFFF
+}
+
+div.highlight .-Color[class*=-C232] {
+ color: #080808
+}
+
+div.highlight .-Color[class*=-BGC232] {
+ background-color: #080808
+}
+
+div.highlight .-Color[class*=-C233] {
+ color: #121212
+}
+
+div.highlight .-Color[class*=-BGC233] {
+ background-color: #121212
+}
+
+div.highlight .-Color[class*=-C234] {
+ color: #1C1C1C
+}
+
+div.highlight .-Color[class*=-BGC234] {
+ background-color: #1C1C1C
+}
+
+div.highlight .-Color[class*=-C235] {
+ color: #262626
+}
+
+div.highlight .-Color[class*=-BGC235] {
+ background-color: #262626
+}
+
+div.highlight .-Color[class*=-C236] {
+ color: #303030
+}
+
+div.highlight .-Color[class*=-BGC236] {
+ background-color: #303030
+}
+
+div.highlight .-Color[class*=-C237] {
+ color: #3A3A3A
+}
+
+div.highlight .-Color[class*=-BGC237] {
+ background-color: #3A3A3A
+}
+
+div.highlight .-Color[class*=-C238] {
+ color: #444444
+}
+
+div.highlight .-Color[class*=-BGC238] {
+ background-color: #444444
+}
+
+div.highlight .-Color[class*=-C239] {
+ color: #4E4E4E
+}
+
+div.highlight .-Color[class*=-BGC239] {
+ background-color: #4E4E4E
+}
+
+div.highlight .-Color[class*=-C240] {
+ color: #585858
+}
+
+div.highlight .-Color[class*=-BGC240] {
+ background-color: #585858
+}
+
+div.highlight .-Color[class*=-C241] {
+ color: #626262
+}
+
+div.highlight .-Color[class*=-BGC241] {
+ background-color: #626262
+}
+
+div.highlight .-Color[class*=-C242] {
+ color: #6C6C6C
+}
+
+div.highlight .-Color[class*=-BGC242] {
+ background-color: #6C6C6C
+}
+
+div.highlight .-Color[class*=-C243] {
+ color: #767676
+}
+
+div.highlight .-Color[class*=-BGC243] {
+ background-color: #767676
+}
+
+div.highlight .-Color[class*=-C244] {
+ color: #808080
+}
+
+div.highlight .-Color[class*=-BGC244] {
+ background-color: #808080
+}
+
+div.highlight .-Color[class*=-C245] {
+ color: #8A8A8A
+}
+
+div.highlight .-Color[class*=-BGC245] {
+ background-color: #8A8A8A
+}
+
+div.highlight .-Color[class*=-C246] {
+ color: #949494
+}
+
+div.highlight .-Color[class*=-BGC246] {
+ background-color: #949494
+}
+
+div.highlight .-Color[class*=-C247] {
+ color: #9E9E9E
+}
+
+div.highlight .-Color[class*=-BGC247] {
+ background-color: #9E9E9E
+}
+
+div.highlight .-Color[class*=-C248] {
+ color: #A8A8A8
+}
+
+div.highlight .-Color[class*=-BGC248] {
+ background-color: #A8A8A8
+}
+
+div.highlight .-Color[class*=-C249] {
+ color: #B2B2B2
+}
+
+div.highlight .-Color[class*=-BGC249] {
+ background-color: #B2B2B2
+}
+
+div.highlight .-Color[class*=-C250] {
+ color: #BCBCBC
+}
+
+div.highlight .-Color[class*=-BGC250] {
+ background-color: #BCBCBC
+}
+
+div.highlight .-Color[class*=-C251] {
+ color: #C6C6C6
+}
+
+div.highlight .-Color[class*=-BGC251] {
+ background-color: #C6C6C6
+}
+
+div.highlight .-Color[class*=-C252] {
+ color: #D0D0D0
+}
+
+div.highlight .-Color[class*=-BGC252] {
+ background-color: #D0D0D0
+}
+
+div.highlight .-Color[class*=-C253] {
+ color: #DADADA
+}
+
+div.highlight .-Color[class*=-BGC253] {
+ background-color: #DADADA
+}
+
+div.highlight .-Color[class*=-C254] {
+ color: #E4E4E4
+}
+
+div.highlight .-Color[class*=-BGC254] {
+ background-color: #E4E4E4
+}
+
+div.highlight .-Color[class*=-C255] {
+ color: #EEEEEE
+}
+
+div.highlight .-Color[class*=-BGC255] {
+ background-color: #EEEEEE
+}
diff --git a/branch/main/singlehtml/_static/plus.png b/branch/main/singlehtml/_static/plus.png
new file mode 100644
index 0000000..7107cec
Binary files /dev/null and b/branch/main/singlehtml/_static/plus.png differ
diff --git a/branch/main/singlehtml/_static/pygments.css b/branch/main/singlehtml/_static/pygments.css
new file mode 100644
index 0000000..6f8b210
--- /dev/null
+++ b/branch/main/singlehtml/_static/pygments.css
@@ -0,0 +1,75 @@
+pre { line-height: 125%; }
+td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
+span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
+td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
+span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
+.highlight .hll { background-color: #ffffcc }
+.highlight { background: #f8f8f8; }
+.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */
+.highlight .err { border: 1px solid #F00 } /* Error */
+.highlight .k { color: #008000; font-weight: bold } /* Keyword */
+.highlight .o { color: #666 } /* Operator */
+.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */
+.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */
+.highlight .cp { color: #9C6500 } /* Comment.Preproc */
+.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */
+.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */
+.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */
+.highlight .gd { color: #A00000 } /* Generic.Deleted */
+.highlight .ge { font-style: italic } /* Generic.Emph */
+.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
+.highlight .gr { color: #E40000 } /* Generic.Error */
+.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.highlight .gi { color: #008400 } /* Generic.Inserted */
+.highlight .go { color: #717171 } /* Generic.Output */
+.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
+.highlight .gs { font-weight: bold } /* Generic.Strong */
+.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.highlight .gt { color: #04D } /* Generic.Traceback */
+.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
+.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
+.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
+.highlight .kp { color: #008000 } /* Keyword.Pseudo */
+.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
+.highlight .kt { color: #B00040 } /* Keyword.Type */
+.highlight .m { color: #666 } /* Literal.Number */
+.highlight .s { color: #BA2121 } /* Literal.String */
+.highlight .na { color: #687822 } /* Name.Attribute */
+.highlight .nb { color: #008000 } /* Name.Builtin */
+.highlight .nc { color: #00F; font-weight: bold } /* Name.Class */
+.highlight .no { color: #800 } /* Name.Constant */
+.highlight .nd { color: #A2F } /* Name.Decorator */
+.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */
+.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */
+.highlight .nf { color: #00F } /* Name.Function */
+.highlight .nl { color: #767600 } /* Name.Label */
+.highlight .nn { color: #00F; font-weight: bold } /* Name.Namespace */
+.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */
+.highlight .nv { color: #19177C } /* Name.Variable */
+.highlight .ow { color: #A2F; font-weight: bold } /* Operator.Word */
+.highlight .w { color: #BBB } /* Text.Whitespace */
+.highlight .mb { color: #666 } /* Literal.Number.Bin */
+.highlight .mf { color: #666 } /* Literal.Number.Float */
+.highlight .mh { color: #666 } /* Literal.Number.Hex */
+.highlight .mi { color: #666 } /* Literal.Number.Integer */
+.highlight .mo { color: #666 } /* Literal.Number.Oct */
+.highlight .sa { color: #BA2121 } /* Literal.String.Affix */
+.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */
+.highlight .sc { color: #BA2121 } /* Literal.String.Char */
+.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */
+.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
+.highlight .s2 { color: #BA2121 } /* Literal.String.Double */
+.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */
+.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */
+.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */
+.highlight .sx { color: #008000 } /* Literal.String.Other */
+.highlight .sr { color: #A45A77 } /* Literal.String.Regex */
+.highlight .s1 { color: #BA2121 } /* Literal.String.Single */
+.highlight .ss { color: #19177C } /* Literal.String.Symbol */
+.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */
+.highlight .fm { color: #00F } /* Name.Function.Magic */
+.highlight .vc { color: #19177C } /* Name.Variable.Class */
+.highlight .vg { color: #19177C } /* Name.Variable.Global */
+.highlight .vi { color: #19177C } /* Name.Variable.Instance */
+.highlight .vm { color: #19177C } /* Name.Variable.Magic */
+.highlight .il { color: #666 } /* Literal.Number.Integer.Long */
\ No newline at end of file
diff --git a/branch/main/singlehtml/_static/searchtools.js b/branch/main/singlehtml/_static/searchtools.js
new file mode 100644
index 0000000..b08d58c
--- /dev/null
+++ b/branch/main/singlehtml/_static/searchtools.js
@@ -0,0 +1,620 @@
+/*
+ * searchtools.js
+ * ~~~~~~~~~~~~~~~~
+ *
+ * Sphinx JavaScript utilities for the full-text search.
+ *
+ * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+"use strict";
+
+/**
+ * Simple result scoring code.
+ */
+if (typeof Scorer === "undefined") {
+ var Scorer = {
+ // Implement the following function to further tweak the score for each result
+ // The function takes a result array [docname, title, anchor, descr, score, filename]
+ // and returns the new score.
+ /*
+ score: result => {
+ const [docname, title, anchor, descr, score, filename] = result
+ return score
+ },
+ */
+
+ // query matches the full name of an object
+ objNameMatch: 11,
+ // or matches in the last dotted part of the object name
+ objPartialMatch: 6,
+ // Additive scores depending on the priority of the object
+ objPrio: {
+ 0: 15, // used to be importantResults
+ 1: 5, // used to be objectResults
+ 2: -5, // used to be unimportantResults
+ },
+ // Used when the priority is not in the mapping.
+ objPrioDefault: 0,
+
+ // query found in title
+ title: 15,
+ partialTitle: 7,
+ // query found in terms
+ term: 5,
+ partialTerm: 2,
+ };
+}
+
+const _removeChildren = (element) => {
+ while (element && element.lastChild) element.removeChild(element.lastChild);
+};
+
+/**
+ * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
+ */
+const _escapeRegExp = (string) =>
+ string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
+
+const _displayItem = (item, searchTerms, highlightTerms) => {
+ const docBuilder = DOCUMENTATION_OPTIONS.BUILDER;
+ const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX;
+ const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX;
+ const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY;
+ const contentRoot = document.documentElement.dataset.content_root;
+
+ const [docName, title, anchor, descr, score, _filename] = item;
+
+ let listItem = document.createElement("li");
+ let requestUrl;
+ let linkUrl;
+ if (docBuilder === "dirhtml") {
+ // dirhtml builder
+ let dirname = docName + "/";
+ if (dirname.match(/\/index\/$/))
+ dirname = dirname.substring(0, dirname.length - 6);
+ else if (dirname === "index/") dirname = "";
+ requestUrl = contentRoot + dirname;
+ linkUrl = requestUrl;
+ } else {
+ // normal html builders
+ requestUrl = contentRoot + docName + docFileSuffix;
+ linkUrl = docName + docLinkSuffix;
+ }
+ let linkEl = listItem.appendChild(document.createElement("a"));
+ linkEl.href = linkUrl + anchor;
+ linkEl.dataset.score = score;
+ linkEl.innerHTML = title;
+ if (descr) {
+ listItem.appendChild(document.createElement("span")).innerHTML =
+ " (" + descr + ")";
+ // highlight search terms in the description
+ if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
+ highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
+ }
+ else if (showSearchSummary)
+ fetch(requestUrl)
+ .then((responseData) => responseData.text())
+ .then((data) => {
+ if (data)
+ listItem.appendChild(
+ Search.makeSearchSummary(data, searchTerms, anchor)
+ );
+ // highlight search terms in the summary
+ if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
+ highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
+ });
+ Search.output.appendChild(listItem);
+};
+const _finishSearch = (resultCount) => {
+ Search.stopPulse();
+ Search.title.innerText = _("Search Results");
+ if (!resultCount)
+ Search.status.innerText = Documentation.gettext(
+ "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories."
+ );
+ else
+ Search.status.innerText = _(
+ "Search finished, found ${resultCount} page(s) matching the search query."
+ ).replace('${resultCount}', resultCount);
+};
+const _displayNextItem = (
+ results,
+ resultCount,
+ searchTerms,
+ highlightTerms,
+) => {
+ // results left, load the summary and display it
+ // this is intended to be dynamic (don't sub resultsCount)
+ if (results.length) {
+ _displayItem(results.pop(), searchTerms, highlightTerms);
+ setTimeout(
+ () => _displayNextItem(results, resultCount, searchTerms, highlightTerms),
+ 5
+ );
+ }
+ // search finished, update title and status message
+ else _finishSearch(resultCount);
+};
+// Helper function used by query() to order search results.
+// Each input is an array of [docname, title, anchor, descr, score, filename].
+// Order the results by score (in opposite order of appearance, since the
+// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically.
+const _orderResultsByScoreThenName = (a, b) => {
+ const leftScore = a[4];
+ const rightScore = b[4];
+ if (leftScore === rightScore) {
+ // same score: sort alphabetically
+ const leftTitle = a[1].toLowerCase();
+ const rightTitle = b[1].toLowerCase();
+ if (leftTitle === rightTitle) return 0;
+ return leftTitle > rightTitle ? -1 : 1; // inverted is intentional
+ }
+ return leftScore > rightScore ? 1 : -1;
+};
+
+/**
+ * Default splitQuery function. Can be overridden in ``sphinx.search`` with a
+ * custom function per language.
+ *
+ * The regular expression works by splitting the string on consecutive characters
+ * that are not Unicode letters, numbers, underscores, or emoji characters.
+ * This is the same as ``\W+`` in Python, preserving the surrogate pair area.
+ */
+if (typeof splitQuery === "undefined") {
+ var splitQuery = (query) => query
+ .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu)
+ .filter(term => term) // remove remaining empty strings
+}
+
+/**
+ * Search Module
+ */
+const Search = {
+ _index: null,
+ _queued_query: null,
+ _pulse_status: -1,
+
+ htmlToText: (htmlString, anchor) => {
+ const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html');
+ for (const removalQuery of [".headerlink", "script", "style"]) {
+ htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() });
+ }
+ if (anchor) {
+ const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`);
+ if (anchorContent) return anchorContent.textContent;
+
+ console.warn(
+ `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.`
+ );
+ }
+
+ // if anchor not specified or not found, fall back to main content
+ const docContent = htmlElement.querySelector('[role="main"]');
+ if (docContent) return docContent.textContent;
+
+ console.warn(
+ "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template."
+ );
+ return "";
+ },
+
+ init: () => {
+ const query = new URLSearchParams(window.location.search).get("q");
+ document
+ .querySelectorAll('input[name="q"]')
+ .forEach((el) => (el.value = query));
+ if (query) Search.performSearch(query);
+ },
+
+ loadIndex: (url) =>
+ (document.body.appendChild(document.createElement("script")).src = url),
+
+ setIndex: (index) => {
+ Search._index = index;
+ if (Search._queued_query !== null) {
+ const query = Search._queued_query;
+ Search._queued_query = null;
+ Search.query(query);
+ }
+ },
+
+ hasIndex: () => Search._index !== null,
+
+ deferQuery: (query) => (Search._queued_query = query),
+
+ stopPulse: () => (Search._pulse_status = -1),
+
+ startPulse: () => {
+ if (Search._pulse_status >= 0) return;
+
+ const pulse = () => {
+ Search._pulse_status = (Search._pulse_status + 1) % 4;
+ Search.dots.innerText = ".".repeat(Search._pulse_status);
+ if (Search._pulse_status >= 0) window.setTimeout(pulse, 500);
+ };
+ pulse();
+ },
+
+ /**
+ * perform a search for something (or wait until index is loaded)
+ */
+ performSearch: (query) => {
+ // create the required interface elements
+ const searchText = document.createElement("h2");
+ searchText.textContent = _("Searching");
+ const searchSummary = document.createElement("p");
+ searchSummary.classList.add("search-summary");
+ searchSummary.innerText = "";
+ const searchList = document.createElement("ul");
+ searchList.classList.add("search");
+
+ const out = document.getElementById("search-results");
+ Search.title = out.appendChild(searchText);
+ Search.dots = Search.title.appendChild(document.createElement("span"));
+ Search.status = out.appendChild(searchSummary);
+ Search.output = out.appendChild(searchList);
+
+ const searchProgress = document.getElementById("search-progress");
+ // Some themes don't use the search progress node
+ if (searchProgress) {
+ searchProgress.innerText = _("Preparing search...");
+ }
+ Search.startPulse();
+
+ // index already loaded, the browser was quick!
+ if (Search.hasIndex()) Search.query(query);
+ else Search.deferQuery(query);
+ },
+
+ _parseQuery: (query) => {
+ // stem the search terms and add them to the correct list
+ const stemmer = new Stemmer();
+ const searchTerms = new Set();
+ const excludedTerms = new Set();
+ const highlightTerms = new Set();
+ const objectTerms = new Set(splitQuery(query.toLowerCase().trim()));
+ splitQuery(query.trim()).forEach((queryTerm) => {
+ const queryTermLower = queryTerm.toLowerCase();
+
+ // maybe skip this "word"
+ // stopwords array is from language_data.js
+ if (
+ stopwords.indexOf(queryTermLower) !== -1 ||
+ queryTerm.match(/^\d+$/)
+ )
+ return;
+
+ // stem the word
+ let word = stemmer.stemWord(queryTermLower);
+ // select the correct list
+ if (word[0] === "-") excludedTerms.add(word.substr(1));
+ else {
+ searchTerms.add(word);
+ highlightTerms.add(queryTermLower);
+ }
+ });
+
+ if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js
+ localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" "))
+ }
+
+ // console.debug("SEARCH: searching for:");
+ // console.info("required: ", [...searchTerms]);
+ // console.info("excluded: ", [...excludedTerms]);
+
+ return [query, searchTerms, excludedTerms, highlightTerms, objectTerms];
+ },
+
+ /**
+ * execute search (requires search index to be loaded)
+ */
+ _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => {
+ const filenames = Search._index.filenames;
+ const docNames = Search._index.docnames;
+ const titles = Search._index.titles;
+ const allTitles = Search._index.alltitles;
+ const indexEntries = Search._index.indexentries;
+
+ // Collect multiple result groups to be sorted separately and then ordered.
+ // Each is an array of [docname, title, anchor, descr, score, filename].
+ const normalResults = [];
+ const nonMainIndexResults = [];
+
+ _removeChildren(document.getElementById("search-progress"));
+
+ const queryLower = query.toLowerCase().trim();
+ for (const [title, foundTitles] of Object.entries(allTitles)) {
+ if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) {
+ for (const [file, id] of foundTitles) {
+ const score = Math.round(Scorer.title * queryLower.length / title.length);
+ const boost = titles[file] === title ? 1 : 0; // add a boost for document titles
+ normalResults.push([
+ docNames[file],
+ titles[file] !== title ? `${titles[file]} > ${title}` : title,
+ id !== null ? "#" + id : "",
+ null,
+ score + boost,
+ filenames[file],
+ ]);
+ }
+ }
+ }
+
+ // search for explicit entries in index directives
+ for (const [entry, foundEntries] of Object.entries(indexEntries)) {
+ if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) {
+ for (const [file, id, isMain] of foundEntries) {
+ const score = Math.round(100 * queryLower.length / entry.length);
+ const result = [
+ docNames[file],
+ titles[file],
+ id ? "#" + id : "",
+ null,
+ score,
+ filenames[file],
+ ];
+ if (isMain) {
+ normalResults.push(result);
+ } else {
+ nonMainIndexResults.push(result);
+ }
+ }
+ }
+ }
+
+ // lookup as object
+ objectTerms.forEach((term) =>
+ normalResults.push(...Search.performObjectSearch(term, objectTerms))
+ );
+
+ // lookup as search terms in fulltext
+ normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms));
+
+ // let the scorer override scores with a custom scoring function
+ if (Scorer.score) {
+ normalResults.forEach((item) => (item[4] = Scorer.score(item)));
+ nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item)));
+ }
+
+ // Sort each group of results by score and then alphabetically by name.
+ normalResults.sort(_orderResultsByScoreThenName);
+ nonMainIndexResults.sort(_orderResultsByScoreThenName);
+
+ // Combine the result groups in (reverse) order.
+ // Non-main index entries are typically arbitrary cross-references,
+ // so display them after other results.
+ let results = [...nonMainIndexResults, ...normalResults];
+
+ // remove duplicate search results
+ // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept
+ let seen = new Set();
+ results = results.reverse().reduce((acc, result) => {
+ let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(',');
+ if (!seen.has(resultStr)) {
+ acc.push(result);
+ seen.add(resultStr);
+ }
+ return acc;
+ }, []);
+
+ return results.reverse();
+ },
+
+ query: (query) => {
+ const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query);
+ const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms);
+
+ // for debugging
+ //Search.lastresults = results.slice(); // a copy
+ // console.info("search results:", Search.lastresults);
+
+ // print the results
+ _displayNextItem(results, results.length, searchTerms, highlightTerms);
+ },
+
+ /**
+ * search for object names
+ */
+ performObjectSearch: (object, objectTerms) => {
+ const filenames = Search._index.filenames;
+ const docNames = Search._index.docnames;
+ const objects = Search._index.objects;
+ const objNames = Search._index.objnames;
+ const titles = Search._index.titles;
+
+ const results = [];
+
+ const objectSearchCallback = (prefix, match) => {
+ const name = match[4]
+ const fullname = (prefix ? prefix + "." : "") + name;
+ const fullnameLower = fullname.toLowerCase();
+ if (fullnameLower.indexOf(object) < 0) return;
+
+ let score = 0;
+ const parts = fullnameLower.split(".");
+
+ // check for different match types: exact matches of full name or
+ // "last name" (i.e. last dotted part)
+ if (fullnameLower === object || parts.slice(-1)[0] === object)
+ score += Scorer.objNameMatch;
+ else if (parts.slice(-1)[0].indexOf(object) > -1)
+ score += Scorer.objPartialMatch; // matches in last name
+
+ const objName = objNames[match[1]][2];
+ const title = titles[match[0]];
+
+ // If more than one term searched for, we require other words to be
+ // found in the name/title/description
+ const otherTerms = new Set(objectTerms);
+ otherTerms.delete(object);
+ if (otherTerms.size > 0) {
+ const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase();
+ if (
+ [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0)
+ )
+ return;
+ }
+
+ let anchor = match[3];
+ if (anchor === "") anchor = fullname;
+ else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname;
+
+ const descr = objName + _(", in ") + title;
+
+ // add custom score for some objects according to scorer
+ if (Scorer.objPrio.hasOwnProperty(match[2]))
+ score += Scorer.objPrio[match[2]];
+ else score += Scorer.objPrioDefault;
+
+ results.push([
+ docNames[match[0]],
+ fullname,
+ "#" + anchor,
+ descr,
+ score,
+ filenames[match[0]],
+ ]);
+ };
+ Object.keys(objects).forEach((prefix) =>
+ objects[prefix].forEach((array) =>
+ objectSearchCallback(prefix, array)
+ )
+ );
+ return results;
+ },
+
+ /**
+ * search for full-text terms in the index
+ */
+ performTermsSearch: (searchTerms, excludedTerms) => {
+ // prepare search
+ const terms = Search._index.terms;
+ const titleTerms = Search._index.titleterms;
+ const filenames = Search._index.filenames;
+ const docNames = Search._index.docnames;
+ const titles = Search._index.titles;
+
+ const scoreMap = new Map();
+ const fileMap = new Map();
+
+ // perform the search on the required terms
+ searchTerms.forEach((word) => {
+ const files = [];
+ const arr = [
+ { files: terms[word], score: Scorer.term },
+ { files: titleTerms[word], score: Scorer.title },
+ ];
+ // add support for partial matches
+ if (word.length > 2) {
+ const escapedWord = _escapeRegExp(word);
+ if (!terms.hasOwnProperty(word)) {
+ Object.keys(terms).forEach((term) => {
+ if (term.match(escapedWord))
+ arr.push({ files: terms[term], score: Scorer.partialTerm });
+ });
+ }
+ if (!titleTerms.hasOwnProperty(word)) {
+ Object.keys(titleTerms).forEach((term) => {
+ if (term.match(escapedWord))
+ arr.push({ files: titleTerms[term], score: Scorer.partialTitle });
+ });
+ }
+ }
+
+ // no match but word was a required one
+ if (arr.every((record) => record.files === undefined)) return;
+
+ // found search word in contents
+ arr.forEach((record) => {
+ if (record.files === undefined) return;
+
+ let recordFiles = record.files;
+ if (recordFiles.length === undefined) recordFiles = [recordFiles];
+ files.push(...recordFiles);
+
+ // set score for the word in each file
+ recordFiles.forEach((file) => {
+ if (!scoreMap.has(file)) scoreMap.set(file, {});
+ scoreMap.get(file)[word] = record.score;
+ });
+ });
+
+ // create the mapping
+ files.forEach((file) => {
+ if (!fileMap.has(file)) fileMap.set(file, [word]);
+ else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word);
+ });
+ });
+
+ // now check if the files don't contain excluded terms
+ const results = [];
+ for (const [file, wordList] of fileMap) {
+ // check if all requirements are matched
+
+ // as search terms with length < 3 are discarded
+ const filteredTermCount = [...searchTerms].filter(
+ (term) => term.length > 2
+ ).length;
+ if (
+ wordList.length !== searchTerms.size &&
+ wordList.length !== filteredTermCount
+ )
+ continue;
+
+ // ensure that none of the excluded terms is in the search result
+ if (
+ [...excludedTerms].some(
+ (term) =>
+ terms[term] === file ||
+ titleTerms[term] === file ||
+ (terms[term] || []).includes(file) ||
+ (titleTerms[term] || []).includes(file)
+ )
+ )
+ break;
+
+ // select one (max) score for the file.
+ const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w]));
+ // add result to the result list
+ results.push([
+ docNames[file],
+ titles[file],
+ "",
+ null,
+ score,
+ filenames[file],
+ ]);
+ }
+ return results;
+ },
+
+ /**
+ * helper function to return a node containing the
+ * search summary for a given text. keywords is a list
+ * of stemmed words.
+ */
+ makeSearchSummary: (htmlText, keywords, anchor) => {
+ const text = Search.htmlToText(htmlText, anchor);
+ if (text === "") return null;
+
+ const textLower = text.toLowerCase();
+ const actualStartPosition = [...keywords]
+ .map((k) => textLower.indexOf(k.toLowerCase()))
+ .filter((i) => i > -1)
+ .slice(-1)[0];
+ const startWithContext = Math.max(actualStartPosition - 120, 0);
+
+ const top = startWithContext === 0 ? "" : "...";
+ const tail = startWithContext + 240 < text.length ? "..." : "";
+
+ let summary = document.createElement("p");
+ summary.classList.add("context");
+ summary.textContent = top + text.substr(startWithContext, 240).trim() + tail;
+
+ return summary;
+ },
+};
+
+_ready(Search.init);
diff --git a/branch/main/singlehtml/_static/sphinx_highlight.js b/branch/main/singlehtml/_static/sphinx_highlight.js
new file mode 100644
index 0000000..8a96c69
--- /dev/null
+++ b/branch/main/singlehtml/_static/sphinx_highlight.js
@@ -0,0 +1,154 @@
+/* Highlighting utilities for Sphinx HTML documentation. */
+"use strict";
+
+const SPHINX_HIGHLIGHT_ENABLED = true
+
+/**
+ * highlight a given string on a node by wrapping it in
+ * span elements with the given class name.
+ */
+const _highlight = (node, addItems, text, className) => {
+ if (node.nodeType === Node.TEXT_NODE) {
+ const val = node.nodeValue;
+ const parent = node.parentNode;
+ const pos = val.toLowerCase().indexOf(text);
+ if (
+ pos >= 0 &&
+ !parent.classList.contains(className) &&
+ !parent.classList.contains("nohighlight")
+ ) {
+ let span;
+
+ const closestNode = parent.closest("body, svg, foreignObject");
+ const isInSVG = closestNode && closestNode.matches("svg");
+ if (isInSVG) {
+ span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
+ } else {
+ span = document.createElement("span");
+ span.classList.add(className);
+ }
+
+ span.appendChild(document.createTextNode(val.substr(pos, text.length)));
+ const rest = document.createTextNode(val.substr(pos + text.length));
+ parent.insertBefore(
+ span,
+ parent.insertBefore(
+ rest,
+ node.nextSibling
+ )
+ );
+ node.nodeValue = val.substr(0, pos);
+ /* There may be more occurrences of search term in this node. So call this
+ * function recursively on the remaining fragment.
+ */
+ _highlight(rest, addItems, text, className);
+
+ if (isInSVG) {
+ const rect = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "rect"
+ );
+ const bbox = parent.getBBox();
+ rect.x.baseVal.value = bbox.x;
+ rect.y.baseVal.value = bbox.y;
+ rect.width.baseVal.value = bbox.width;
+ rect.height.baseVal.value = bbox.height;
+ rect.setAttribute("class", className);
+ addItems.push({ parent: parent, target: rect });
+ }
+ }
+ } else if (node.matches && !node.matches("button, select, textarea")) {
+ node.childNodes.forEach((el) => _highlight(el, addItems, text, className));
+ }
+};
+const _highlightText = (thisNode, text, className) => {
+ let addItems = [];
+ _highlight(thisNode, addItems, text, className);
+ addItems.forEach((obj) =>
+ obj.parent.insertAdjacentElement("beforebegin", obj.target)
+ );
+};
+
+/**
+ * Small JavaScript module for the documentation.
+ */
+const SphinxHighlight = {
+
+ /**
+ * highlight the search words provided in localstorage in the text
+ */
+ highlightSearchWords: () => {
+ if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight
+
+ // get and clear terms from localstorage
+ const url = new URL(window.location);
+ const highlight =
+ localStorage.getItem("sphinx_highlight_terms")
+ || url.searchParams.get("highlight")
+ || "";
+ localStorage.removeItem("sphinx_highlight_terms")
+ url.searchParams.delete("highlight");
+ window.history.replaceState({}, "", url);
+
+ // get individual terms from highlight string
+ const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
+ if (terms.length === 0) return; // nothing to do
+
+ // There should never be more than one element matching "div.body"
+ const divBody = document.querySelectorAll("div.body");
+ const body = divBody.length ? divBody[0] : document.querySelector("body");
+ window.setTimeout(() => {
+ terms.forEach((term) => _highlightText(body, term, "highlighted"));
+ }, 10);
+
+ const searchBox = document.getElementById("searchbox");
+ if (searchBox === null) return;
+ searchBox.appendChild(
+ document
+ .createRange()
+ .createContextualFragment(
+ '' +
+ '' +
+ _("Hide Search Matches") +
+ "
"
+ )
+ );
+ },
+
+ /**
+ * helper function to hide the search marks again
+ */
+ hideSearchWords: () => {
+ document
+ .querySelectorAll("#searchbox .highlight-link")
+ .forEach((el) => el.remove());
+ document
+ .querySelectorAll("span.highlighted")
+ .forEach((el) => el.classList.remove("highlighted"));
+ localStorage.removeItem("sphinx_highlight_terms")
+ },
+
+ initEscapeListener: () => {
+ // only install a listener if it is really needed
+ if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return;
+
+ document.addEventListener("keydown", (event) => {
+ // bail for input elements
+ if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
+ // bail with special keys
+ if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;
+ if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) {
+ SphinxHighlight.hideSearchWords();
+ event.preventDefault();
+ }
+ });
+ },
+};
+
+_ready(() => {
+ /* Do not call highlightSearchWords() when we are on the search page.
+ * It will highlight words from the *previous* search query.
+ */
+ if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords();
+ SphinxHighlight.initEscapeListener();
+});
diff --git a/branch/main/singlehtml/_static/sphinx_lesson.css b/branch/main/singlehtml/_static/sphinx_lesson.css
new file mode 100644
index 0000000..68cb32d
--- /dev/null
+++ b/branch/main/singlehtml/_static/sphinx_lesson.css
@@ -0,0 +1,103 @@
+/* sphinx_lesson.css
+ * https://webaim.org/resources/contrastchecker/?fcolor=00000&bcolor=FCE762
+ * */
+:root {
+ --sphinx-lesson-selection-bg-color: #fce762;
+ --sphinx-lesson-selection-fg-color: #000000;
+}
+
+/* https://webaim.org/resources/contrastchecker/?fcolor=FFFFFF&bcolor=745315
+ * when dark theme is selected the some themes use this attirbute
+ */
+html[data-theme='dark'], body[data-theme='dark'] {
+ --sphinx-lesson-selection-bg-color: #745315;
+ --sphinx-lesson-selection-fg-color: #ffffff;
+}
+
+/* when browser/system theme is dark and no theme is selected */
+@media (prefers-color-scheme: dark) {
+ html[data-theme='auto'], body[data-theme='auto'] {
+ --sphinx-lesson-selection-bg-color: #745315;
+ --sphinx-lesson-selection-fg-color: #ffffff;
+ }
+}
+
+body.wy-body-for-nav img.with-border {
+ border: 2px solid;
+}
+
+.rst-content .admonition-no-content {
+ padding-bottom: 0px;
+}
+
+.rst-content .demo > .admonition-title::before {
+ content: "\01F440"; /* Eyes */ }
+.rst-content .type-along > .admonition-title::before {
+ content: "\02328\0FE0F"; /* Keyboard */ }
+.rst-content .exercise > .admonition-title::before {
+ content: "\0270D\0FE0F"; /* Hand */ }
+.rst-content .solution > .admonition-title::before {
+ content: "\02714\0FE0E"; /* Check mark */ }
+.rst-content .homework > .admonition-title::before {
+ content: "\01F4DD"; /* Memo */ }
+.rst-content .discussion > .admonition-title::before {
+ content: "\01F4AC"; /* Speech balloon */ }
+.rst-content .questions > .admonition-title::before {
+ content: "\02753\0FE0E"; /* Question mark */ }
+.rst-content .prerequisites > .admonition-title::before {
+ content: "\02699"; /* Gear */ }
+.rst-content .seealso > .admonition-title::before {
+ content: "\027A1\0FE0E"; /* Question mark */ }
+
+
+/* instructor-note */
+.rst-content .instructor-note {
+ background: #e7e7e7;
+}
+.rst-content .instructor-note > .admonition-title {
+ background: #6a6a6a;
+}
+.rst-content .instructor-note > .admonition-title::before {
+ content: "";
+}
+
+
+/* sphinx_toggle_button, make the font white */
+.rst-content .toggle.admonition button.toggle-button {
+ color: white;
+}
+
+/* sphinx-togglebutton, remove underflow when toggled to hidden mode */
+.rst-content .admonition.toggle-hidden {
+ padding-bottom: 0px;
+}
+
+/* selection / highlight colour uses a yellow background and a black text */
+/*** Works on common browsers ***/
+::selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
+
+/*** Mozilla based browsers ***/
+::-moz-selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
+
+/***For Other Browsers ***/
+::-o-selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
+
+::-ms-selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
+
+/*** For Webkit ***/
+::-webkit-selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
diff --git a/branch/main/singlehtml/_static/sphinx_rtd_theme_ext_color_contrast.css b/branch/main/singlehtml/_static/sphinx_rtd_theme_ext_color_contrast.css
new file mode 100644
index 0000000..e68feb8
--- /dev/null
+++ b/branch/main/singlehtml/_static/sphinx_rtd_theme_ext_color_contrast.css
@@ -0,0 +1,47 @@
+/* The following are for web accessibility of sphinx_rtd_theme: they
+ * solve some of the most frequent contrast issues. Remove when this
+ * solved:
+ * https://github.com/readthedocs/sphinx_rtd_theme/issues/971
+ */
+/* background: #fcfcfc, note boxes #E7F2FA */
+a { color: #2573A7; } /* original #2980B9, #1F5C84; */
+body { color: #242424; } /* original #404040, #383838 */
+.wy-side-nav-search>a, .wy-side-nav-search .wy-dropdown>a {
+ color: #ffffff;
+} /* original #fcfcfc */
+footer { color: #737373; } /* original gray=#808080*/
+footer span.commit code, footer span.commit .rst-content tt, .rst-content footer span.commit tt {
+ color: #737373;
+} /* original gray=#808080*/
+.rst-content tt.literal, .rst-content tt.literal, .rst-content code.literal {
+ color: #AB2314;
+}
+/* Sidebar background */
+.wy-side-nav-search { background-color: #277CB4;}
+
+/* Same, but for pygments */
+.highlight .ch { color: #3E7A89; } /* #! line */
+.highlight .c1 { color: #3E7A89; } /* also comments */
+.highlight .nv { color: #AD3ECC; } /* variable */
+.highlight .gp { color: #B45608; } /* prompt character, $*/
+.highlight .si { color: #3975B1; } /* ${} variable text */
+.highlight .nc { color: #0C78A7; }
+
+/* Sphinx admonitions */
+/* warning */
+.wy-alert.wy-alert-warning .wy-alert-title, .rst-content .wy-alert-warning.note .wy-alert-title, .rst-content .attention .wy-alert-title, .rst-content .caution .wy-alert-title, .rst-content .wy-alert-warning.danger .wy-alert-title, .rst-content .wy-alert-warning.error .wy-alert-title, .rst-content .wy-alert-warning.hint .wy-alert-title, .rst-content .wy-alert-warning.important .wy-alert-title, .rst-content .wy-alert-warning.tip .wy-alert-title, .rst-content .warning .wy-alert-title, .rst-content .wy-alert-warning.seealso .wy-alert-title, .rst-content .admonition-todo .wy-alert-title, .rst-content .wy-alert-warning.admonition .wy-alert-title, .wy-alert.wy-alert-warning .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-warning .admonition-title, .rst-content .wy-alert-warning.note .admonition-title, .rst-content .attention .admonition-title, .rst-content .caution .admonition-title, .rst-content .wy-alert-warning.danger .admonition-title, .rst-content .wy-alert-warning.error .admonition-title, .rst-content .wy-alert-warning.hint .admonition-title, .rst-content .wy-alert-warning.important .admonition-title, .rst-content .wy-alert-warning.tip .admonition-title, .rst-content .warning .admonition-title, .rst-content .wy-alert-warning.seealso .admonition-title, .rst-content .admonition-todo .admonition-title, .rst-content .wy-alert-warning.admonition .admonition-title {
+ background: #B15E16; }
+/* important */
+.wy-alert.wy-alert-success .wy-alert-title, .rst-content .wy-alert-success.note .wy-alert-title, .rst-content .wy-alert-success.attention .wy-alert-title, .rst-content .wy-alert-success.caution .wy-alert-title, .rst-content .wy-alert-success.danger .wy-alert-title, .rst-content .wy-alert-success.error .wy-alert-title, .rst-content .hint .wy-alert-title, .rst-content .important .wy-alert-title, .rst-content .tip .wy-alert-title, .rst-content .wy-alert-success.warning .wy-alert-title, .rst-content .wy-alert-success.seealso .wy-alert-title, .rst-content .wy-alert-success.admonition-todo .wy-alert-title, .rst-content .wy-alert-success.admonition .wy-alert-title, .wy-alert.wy-alert-success .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-success .admonition-title, .rst-content .wy-alert-success.note .admonition-title, .rst-content .wy-alert-success.attention .admonition-title, .rst-content .wy-alert-success.caution .admonition-title, .rst-content .wy-alert-success.danger .admonition-title, .rst-content .wy-alert-success.error .admonition-title, .rst-content .hint .admonition-title, .rst-content .important .admonition-title, .rst-content .tip .admonition-title, .rst-content .wy-alert-success.warning .admonition-title, .rst-content .wy-alert-success.seealso .admonition-title, .rst-content .wy-alert-success.admonition-todo .admonition-title, .rst-content .wy-alert-success.admonition .admonition-title {
+ background: #12826C; }
+/* seealso, note, etc */
+.wy-alert.wy-alert-info .wy-alert-title, .rst-content .note .wy-alert-title, .rst-content .wy-alert-info.attention .wy-alert-title, .rst-content .wy-alert-info.caution .wy-alert-title, .rst-content .wy-alert-info.danger .wy-alert-title, .rst-content .wy-alert-info.error .wy-alert-title, .rst-content .wy-alert-info.hint .wy-alert-title, .rst-content .wy-alert-info.important .wy-alert-title, .rst-content .wy-alert-info.tip .wy-alert-title, .rst-content .wy-alert-info.warning .wy-alert-title, .rst-content .seealso .wy-alert-title, .rst-content .wy-alert-info.admonition-todo .wy-alert-title, .rst-content .wy-alert-info.admonition .wy-alert-title, .wy-alert.wy-alert-info .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-info .admonition-title, .rst-content .note .admonition-title, .rst-content .wy-alert-info.attention .admonition-title, .rst-content .wy-alert-info.caution .admonition-title, .rst-content .wy-alert-info.danger .admonition-title, .rst-content .wy-alert-info.error .admonition-title, .rst-content .wy-alert-info.hint .admonition-title, .rst-content .wy-alert-info.important .admonition-title, .rst-content .wy-alert-info.tip .admonition-title, .rst-content .wy-alert-info.warning .admonition-title, .rst-content .seealso .admonition-title, .rst-content .wy-alert-info.admonition-todo .admonition-title, .rst-content .wy-alert-info.admonition .admonition-title {
+ background: #277CB4; }
+/* error, danger */
+.rst-content .danger .admonition-title, .rst-content .danger .wy-alert-title, .rst-content .error .admonition-title, .rst-content .error .wy-alert-title, .rst-content .wy-alert-danger.admonition-todo .admonition-title, .rst-content .wy-alert-danger.admonition-todo .wy-alert-title, .rst-content .wy-alert-danger.admonition .admonition-title, .rst-content .wy-alert-danger.admonition .wy-alert-title, .rst-content .wy-alert-danger.attention .admonition-title, .rst-content .wy-alert-danger.attention .wy-alert-title, .rst-content .wy-alert-danger.caution .admonition-title, .rst-content .wy-alert-danger.caution .wy-alert-title, .rst-content .wy-alert-danger.hint .admonition-title, .rst-content .wy-alert-danger.hint .wy-alert-title, .rst-content .wy-alert-danger.important .admonition-title, .rst-content .wy-alert-danger.important .wy-alert-title, .rst-content .wy-alert-danger.note .admonition-title, .rst-content .wy-alert-danger.note .wy-alert-title, .rst-content .wy-alert-danger.seealso .admonition-title, .rst-content .wy-alert-danger.seealso .wy-alert-title, .rst-content .wy-alert-danger.tip .admonition-title, .rst-content .wy-alert-danger.tip .wy-alert-title, .rst-content .wy-alert-danger.warning .admonition-title, .rst-content .wy-alert-danger.warning .wy-alert-title, .rst-content .wy-alert.wy-alert-danger .admonition-title, .wy-alert.wy-alert-danger .rst-content .admonition-title, .wy-alert.wy-alert-danger .wy-alert-title {
+ background: #e31704;
+}
+
+/* Generic admonition titles */
+.wy-alert-title, .rst-content .admonition-title {
+ background: #277CB4; }
diff --git a/branch/main/singlehtml/_static/style.css b/branch/main/singlehtml/_static/style.css
new file mode 100644
index 0000000..59e60ee
--- /dev/null
+++ b/branch/main/singlehtml/_static/style.css
@@ -0,0 +1,6 @@
+.rst-content .objectives {
+ background: #fee0d2;
+}
+.rst-content .objectives > .admonition-title {
+ background: #fc9272;
+}
diff --git a/branch/main/singlehtml/_static/tabs.css b/branch/main/singlehtml/_static/tabs.css
new file mode 100644
index 0000000..957ba60
--- /dev/null
+++ b/branch/main/singlehtml/_static/tabs.css
@@ -0,0 +1,89 @@
+.sphinx-tabs {
+ margin-bottom: 1rem;
+}
+
+[role="tablist"] {
+ border-bottom: 1px solid #a0b3bf;
+}
+
+.sphinx-tabs-tab {
+ position: relative;
+ font-family: Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;
+ color: #1D5C87;
+ line-height: 24px;
+ margin: 0;
+ font-size: 16px;
+ font-weight: 400;
+ background-color: rgba(255, 255, 255, 0);
+ border-radius: 5px 5px 0 0;
+ border: 0;
+ padding: 1rem 1.5rem;
+ margin-bottom: 0;
+}
+
+.sphinx-tabs-tab[aria-selected="true"] {
+ font-weight: 700;
+ border: 1px solid #a0b3bf;
+ border-bottom: 1px solid white;
+ margin: -1px;
+ background-color: white;
+}
+
+.sphinx-tabs-tab:focus {
+ z-index: 1;
+ outline-offset: 1px;
+}
+
+.sphinx-tabs-panel {
+ position: relative;
+ padding: 1rem;
+ border: 1px solid #a0b3bf;
+ margin: 0px -1px -1px -1px;
+ border-radius: 0 0 5px 5px;
+ border-top: 0;
+ background: white;
+}
+
+.sphinx-tabs-panel.code-tab {
+ padding: 0.4rem;
+}
+
+.sphinx-tab img {
+ margin-bottom: 24 px;
+}
+
+/* Dark theme preference styling */
+
+@media (prefers-color-scheme: dark) {
+ body[data-theme="auto"] .sphinx-tabs-panel {
+ color: white;
+ background-color: rgb(50, 50, 50);
+ }
+
+ body[data-theme="auto"] .sphinx-tabs-tab {
+ color: white;
+ background-color: rgba(255, 255, 255, 0.05);
+ }
+
+ body[data-theme="auto"] .sphinx-tabs-tab[aria-selected="true"] {
+ border-bottom: 1px solid rgb(50, 50, 50);
+ background-color: rgb(50, 50, 50);
+ }
+}
+
+/* Explicit dark theme styling */
+
+body[data-theme="dark"] .sphinx-tabs-panel {
+ color: white;
+ background-color: rgb(50, 50, 50);
+}
+
+body[data-theme="dark"] .sphinx-tabs-tab {
+ color: white;
+ background-color: rgba(255, 255, 255, 0.05);
+}
+
+body[data-theme="dark"] .sphinx-tabs-tab[aria-selected="true"] {
+ border-bottom: 2px solid rgb(50, 50, 50);
+ background-color: rgb(50, 50, 50);
+}
diff --git a/branch/main/singlehtml/_static/tabs.js b/branch/main/singlehtml/_static/tabs.js
new file mode 100644
index 0000000..48dc303
--- /dev/null
+++ b/branch/main/singlehtml/_static/tabs.js
@@ -0,0 +1,145 @@
+try {
+ var session = window.sessionStorage || {};
+} catch (e) {
+ var session = {};
+}
+
+window.addEventListener("DOMContentLoaded", () => {
+ const allTabs = document.querySelectorAll('.sphinx-tabs-tab');
+ const tabLists = document.querySelectorAll('[role="tablist"]');
+
+ allTabs.forEach(tab => {
+ tab.addEventListener("click", changeTabs);
+ });
+
+ tabLists.forEach(tabList => {
+ tabList.addEventListener("keydown", keyTabs);
+ });
+
+ // Restore group tab selection from session
+ const lastSelected = session.getItem('sphinx-tabs-last-selected');
+ if (lastSelected != null) selectNamedTabs(lastSelected);
+});
+
+/**
+ * Key focus left and right between sibling elements using arrows
+ * @param {Node} e the element in focus when key was pressed
+ */
+function keyTabs(e) {
+ const tab = e.target;
+ let nextTab = null;
+ if (e.keyCode === 39 || e.keyCode === 37) {
+ tab.setAttribute("tabindex", -1);
+ // Move right
+ if (e.keyCode === 39) {
+ nextTab = tab.nextElementSibling;
+ if (nextTab === null) {
+ nextTab = tab.parentNode.firstElementChild;
+ }
+ // Move left
+ } else if (e.keyCode === 37) {
+ nextTab = tab.previousElementSibling;
+ if (nextTab === null) {
+ nextTab = tab.parentNode.lastElementChild;
+ }
+ }
+ }
+
+ if (nextTab !== null) {
+ nextTab.setAttribute("tabindex", 0);
+ nextTab.focus();
+ }
+}
+
+/**
+ * Select or deselect clicked tab. If a group tab
+ * is selected, also select tab in other tabLists.
+ * @param {Node} e the element that was clicked
+ */
+function changeTabs(e) {
+ // Use this instead of the element that was clicked, in case it's a child
+ const notSelected = this.getAttribute("aria-selected") === "false";
+ const positionBefore = this.parentNode.getBoundingClientRect().top;
+ const notClosable = !this.parentNode.classList.contains("closeable");
+
+ deselectTabList(this);
+
+ if (notSelected || notClosable) {
+ selectTab(this);
+ const name = this.getAttribute("name");
+ selectNamedTabs(name, this.id);
+
+ if (this.classList.contains("group-tab")) {
+ // Persist during session
+ session.setItem('sphinx-tabs-last-selected', name);
+ }
+ }
+
+ const positionAfter = this.parentNode.getBoundingClientRect().top;
+ const positionDelta = positionAfter - positionBefore;
+ // Scroll to offset content resizing
+ window.scrollTo(0, window.scrollY + positionDelta);
+}
+
+/**
+ * Select tab and show associated panel.
+ * @param {Node} tab tab to select
+ */
+function selectTab(tab) {
+ tab.setAttribute("aria-selected", true);
+
+ // Show the associated panel
+ document
+ .getElementById(tab.getAttribute("aria-controls"))
+ .removeAttribute("hidden");
+}
+
+/**
+ * Hide the panels associated with all tabs within the
+ * tablist containing this tab.
+ * @param {Node} tab a tab within the tablist to deselect
+ */
+function deselectTabList(tab) {
+ const parent = tab.parentNode;
+ const grandparent = parent.parentNode;
+
+ Array.from(parent.children)
+ .forEach(t => t.setAttribute("aria-selected", false));
+
+ Array.from(grandparent.children)
+ .slice(1) // Skip tablist
+ .forEach(panel => panel.setAttribute("hidden", true));
+}
+
+/**
+ * Select grouped tabs with the same name, but no the tab
+ * with the given id.
+ * @param {Node} name name of grouped tab to be selected
+ * @param {Node} clickedId id of clicked tab
+ */
+function selectNamedTabs(name, clickedId=null) {
+ const groupedTabs = document.querySelectorAll(`.sphinx-tabs-tab[name="${name}"]`);
+ const tabLists = Array.from(groupedTabs).map(tab => tab.parentNode);
+
+ tabLists
+ .forEach(tabList => {
+ // Don't want to change the tabList containing the clicked tab
+ const clickedTab = tabList.querySelector(`[id="${clickedId}"]`);
+ if (clickedTab === null ) {
+ // Select first tab with matching name
+ const tab = tabList.querySelector(`.sphinx-tabs-tab[name="${name}"]`);
+ deselectTabList(tab);
+ selectTab(tab);
+ }
+ })
+}
+
+if (typeof exports === 'undefined') {
+ exports = {};
+}
+
+exports.keyTabs = keyTabs;
+exports.changeTabs = changeTabs;
+exports.selectTab = selectTab;
+exports.deselectTabList = deselectTabList;
+exports.selectNamedTabs = selectNamedTabs;
diff --git a/branch/main/singlehtml/_static/term_role_formatting.css b/branch/main/singlehtml/_static/term_role_formatting.css
new file mode 100644
index 0000000..0b66095
--- /dev/null
+++ b/branch/main/singlehtml/_static/term_role_formatting.css
@@ -0,0 +1,4 @@
+/* Make terms bold */
+a.reference span.std-term {
+ font-weight: bold;
+}
diff --git a/branch/main/singlehtml/_static/togglebutton.css b/branch/main/singlehtml/_static/togglebutton.css
new file mode 100644
index 0000000..54a6787
--- /dev/null
+++ b/branch/main/singlehtml/_static/togglebutton.css
@@ -0,0 +1,160 @@
+/**
+ * Admonition-based toggles
+ */
+
+/* Visibility of the target */
+.admonition.toggle .admonition-title ~ * {
+ transition: opacity .3s, height .3s;
+}
+
+/* Toggle buttons inside admonitions so we see the title */
+.admonition.toggle {
+ position: relative;
+}
+
+/* Titles should cut off earlier to avoid overlapping w/ button */
+.admonition.toggle .admonition-title {
+ padding-right: 25%;
+ cursor: pointer;
+}
+
+/* Hovering will cause a slight shift in color to make it feel interactive */
+.admonition.toggle .admonition-title:hover {
+ box-shadow: inset 0 0 0px 20px rgb(0 0 0 / 1%);
+}
+
+/* Hovering will cause a slight shift in color to make it feel interactive */
+.admonition.toggle .admonition-title:active {
+ box-shadow: inset 0 0 0px 20px rgb(0 0 0 / 3%);
+}
+
+/* Remove extra whitespace below the admonition title when hidden */
+.admonition.toggle-hidden {
+ padding-bottom: 0;
+}
+
+.admonition.toggle-hidden .admonition-title {
+ margin-bottom: 0;
+}
+
+/* hides all the content of a page until de-toggled */
+.admonition.toggle-hidden .admonition-title ~ * {
+ height: 0;
+ margin: 0;
+ opacity: 0;
+ visibility: hidden;
+}
+
+/* General button style and position*/
+button.toggle-button {
+ /**
+ * Background and shape. By default there's no background
+ * but users can style as they wish
+ */
+ background: none;
+ border: none;
+ outline: none;
+
+ /* Positioning just inside the admonition title */
+ position: absolute;
+ right: 0.5em;
+ padding: 0px;
+ border: none;
+ outline: none;
+}
+
+/* Display the toggle hint on wide screens */
+@media (min-width: 768px) {
+ button.toggle-button.toggle-button-hidden:before {
+ content: attr(data-toggle-hint); /* This will be filled in by JS */
+ font-size: .8em;
+ align-self: center;
+ }
+}
+
+/* Icon behavior */
+.tb-icon {
+ transition: transform .2s ease-out;
+ height: 1.5em;
+ width: 1.5em;
+ stroke: currentColor; /* So that we inherit the color of other text */
+}
+
+/* The icon should point right when closed, down when open. */
+/* Open */
+.admonition.toggle button .tb-icon {
+ transform: rotate(90deg);
+}
+
+/* Closed */
+.admonition.toggle button.toggle-button-hidden .tb-icon {
+ transform: rotate(0deg);
+}
+
+/* With details toggles, we don't rotate the icon so it points right */
+details.toggle-details .tb-icon {
+ height: 1.4em;
+ width: 1.4em;
+ margin-top: 0.1em; /* To center the button vertically */
+}
+
+
+/**
+ * Details-based toggles.
+ * In this case, we wrap elements with `.toggle` in a details block.
+ */
+
+/* Details blocks */
+details.toggle-details {
+ margin: 1em 0;
+}
+
+
+details.toggle-details summary {
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ list-style: none;
+ border-radius: .2em;
+ border-left: 3px solid #1976d2;
+ background-color: rgb(204 204 204 / 10%);
+ padding: 0.2em 0.7em 0.3em 0.5em; /* Less padding on left because the SVG has left margin */
+ font-size: 0.9em;
+}
+
+details.toggle-details summary:hover {
+ background-color: rgb(204 204 204 / 20%);
+}
+
+details.toggle-details summary:active {
+ background: rgb(204 204 204 / 28%);
+}
+
+.toggle-details__summary-text {
+ margin-left: 0.2em;
+}
+
+details.toggle-details[open] summary {
+ margin-bottom: .5em;
+}
+
+details.toggle-details[open] summary .tb-icon {
+ transform: rotate(90deg);
+}
+
+details.toggle-details[open] summary ~ * {
+ animation: toggle-fade-in .3s ease-out;
+}
+
+@keyframes toggle-fade-in {
+ from {opacity: 0%;}
+ to {opacity: 100%;}
+}
+
+/* Print rules - we hide all toggle button elements at print */
+@media print {
+ /* Always hide the summary so the button doesn't show up */
+ details.toggle-details summary {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/branch/main/singlehtml/_static/togglebutton.js b/branch/main/singlehtml/_static/togglebutton.js
new file mode 100644
index 0000000..215a7ee
--- /dev/null
+++ b/branch/main/singlehtml/_static/togglebutton.js
@@ -0,0 +1,187 @@
+/**
+ * Add Toggle Buttons to elements
+ */
+
+let toggleChevron = `
+
+
+
+ `;
+
+var initToggleItems = () => {
+ var itemsToToggle = document.querySelectorAll(togglebuttonSelector);
+ console.log(`[togglebutton]: Adding toggle buttons to ${itemsToToggle.length} items`)
+ // Add the button to each admonition and hook up a callback to toggle visibility
+ itemsToToggle.forEach((item, index) => {
+ if (item.classList.contains("admonition")) {
+ // If it's an admonition block, then we'll add a button inside
+ // Generate unique IDs for this item
+ var toggleID = `toggle-${index}`;
+ var buttonID = `button-${toggleID}`;
+
+ item.setAttribute('id', toggleID);
+ if (!item.classList.contains("toggle")){
+ item.classList.add("toggle");
+ }
+ // This is the button that will be added to each item to trigger the toggle
+ var collapseButton = `
+
+ ${toggleChevron}
+ `;
+
+ title = item.querySelector(".admonition-title")
+ title.insertAdjacentHTML("beforeend", collapseButton);
+ thisButton = document.getElementById(buttonID);
+
+ // Add click handlers for the button + admonition title (if admonition)
+ admonitionTitle = document.querySelector(`#${toggleID} > .admonition-title`)
+ if (admonitionTitle) {
+ // If an admonition, then make the whole title block clickable
+ admonitionTitle.addEventListener('click', toggleClickHandler);
+ admonitionTitle.dataset.target = toggleID
+ admonitionTitle.dataset.button = buttonID
+ } else {
+ // If not an admonition then we'll listen for the button click
+ thisButton.addEventListener('click', toggleClickHandler);
+ }
+
+ // Now hide the item for this toggle button unless explicitly noted to show
+ if (!item.classList.contains("toggle-shown")) {
+ toggleHidden(thisButton);
+ }
+ } else {
+ // If not an admonition, wrap the block in a block
+ // Define the structure of the details block and insert it as a sibling
+ var detailsBlock = `
+
+
+ ${toggleChevron}
+ ${toggleHintShow}
+
+ `;
+ item.insertAdjacentHTML("beforebegin", detailsBlock);
+
+ // Now move the toggle-able content inside of the details block
+ details = item.previousElementSibling
+ details.appendChild(item)
+ item.classList.add("toggle-details__container")
+
+ // Set up a click trigger to change the text as needed
+ details.addEventListener('click', (click) => {
+ let parent = click.target.parentElement;
+ if (parent.tagName.toLowerCase() == "details") {
+ summary = parent.querySelector("summary");
+ details = parent;
+ } else {
+ summary = parent;
+ details = parent.parentElement;
+ }
+ // Update the inner text for the proper hint
+ if (details.open) {
+ summary.querySelector("span.toggle-details__summary-text").innerText = toggleHintShow;
+ } else {
+ summary.querySelector("span.toggle-details__summary-text").innerText = toggleHintHide;
+ }
+
+ });
+
+ // If we have a toggle-shown class, open details block should be open
+ if (item.classList.contains("toggle-shown")) {
+ details.click();
+ }
+ }
+ })
+};
+
+// This should simply add / remove the collapsed class and change the button text
+var toggleHidden = (button) => {
+ target = button.dataset['target']
+ var itemToToggle = document.getElementById(target);
+ if (itemToToggle.classList.contains("toggle-hidden")) {
+ itemToToggle.classList.remove("toggle-hidden");
+ button.classList.remove("toggle-button-hidden");
+ } else {
+ itemToToggle.classList.add("toggle-hidden");
+ button.classList.add("toggle-button-hidden");
+ }
+}
+
+var toggleClickHandler = (click) => {
+ // Be cause the admonition title is clickable and extends to the whole admonition
+ // We only look for a click event on this title to trigger the toggle.
+
+ if (click.target.classList.contains("admonition-title")) {
+ button = click.target.querySelector(".toggle-button");
+ } else if (click.target.classList.contains("tb-icon")) {
+ // We've clicked the icon and need to search up one parent for the button
+ button = click.target.parentElement;
+ } else if (click.target.tagName == "polyline") {
+ // We've clicked the SVG elements inside the button, need to up 2 layers
+ button = click.target.parentElement.parentElement;
+ } else if (click.target.classList.contains("toggle-button")) {
+ // We've clicked the button itself and so don't need to do anything
+ button = click.target;
+ } else {
+ console.log(`[togglebutton]: Couldn't find button for ${click.target}`)
+ }
+ target = document.getElementById(button.dataset['button']);
+ toggleHidden(target);
+}
+
+// If we want to blanket-add toggle classes to certain cells
+var addToggleToSelector = () => {
+ const selector = "";
+ if (selector.length > 0) {
+ document.querySelectorAll(selector).forEach((item) => {
+ item.classList.add("toggle");
+ })
+ }
+}
+
+// Helper function to run when the DOM is finished
+const sphinxToggleRunWhenDOMLoaded = cb => {
+ if (document.readyState != 'loading') {
+ cb()
+ } else if (document.addEventListener) {
+ document.addEventListener('DOMContentLoaded', cb)
+ } else {
+ document.attachEvent('onreadystatechange', function() {
+ if (document.readyState == 'complete') cb()
+ })
+ }
+}
+sphinxToggleRunWhenDOMLoaded(addToggleToSelector)
+sphinxToggleRunWhenDOMLoaded(initToggleItems)
+
+/** Toggle details blocks to be open when printing */
+if (toggleOpenOnPrint == "true") {
+ window.addEventListener("beforeprint", () => {
+ // Open the details
+ document.querySelectorAll("details.toggle-details").forEach((el) => {
+ el.dataset["togglestatus"] = el.open;
+ el.open = true;
+ });
+
+ // Open the admonitions
+ document.querySelectorAll(".admonition.toggle.toggle-hidden").forEach((el) => {
+ console.log(el);
+ el.querySelector("button.toggle-button").click();
+ el.dataset["toggle_after_print"] = "true";
+ });
+ });
+ window.addEventListener("afterprint", () => {
+ // Re-close the details that were closed
+ document.querySelectorAll("details.toggle-details").forEach((el) => {
+ el.open = el.dataset["togglestatus"] == "true";
+ delete el.dataset["togglestatus"];
+ });
+
+ // Re-close the admonition toggle buttons
+ document.querySelectorAll(".admonition.toggle").forEach((el) => {
+ if (el.dataset["toggle_after_print"] == "true") {
+ el.querySelector("button.toggle-button").click();
+ delete el.dataset["toggle_after_print"];
+ }
+ });
+ });
+}
diff --git a/branch/main/singlehtml/index.html b/branch/main/singlehtml/index.html
new file mode 100644
index 0000000..abcce9b
--- /dev/null
+++ b/branch/main/singlehtml/index.html
@@ -0,0 +1,5453 @@
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example) documentation
+
+ Edit on GitHub
+
+
+
+
+
+
+
+
+Reproducible research software development using Python (ML example)
+
+Big-picture goal
+This is a hands-on course on research software engineering . In this
+workshop we assume that most workshop participants use Python in their work or
+are leading a group which uses Python. Therefore, some of the examples will use
+Python as the example language.
+We will work with an example project (Example project: 2D classification task using a nearest-neighbor predictor )
+and go through all important steps of a typical
+software project. Once we have seen the building blocks, we will try to apply
+them to own projects .
+
+
Preparation
+
+Get a GitHub account following these instructions .
+You will need a text editor . If you don’t have a favorite one, we recommend
+VS Code .
+If you prefer to work in the terminal and not in VS Code, set up these two (skip this if you use VS Code):
+
+
+Follow Software install instructions (but we will do this together at the beginning of the workshop).
+
+
+
+
+Schedule
+
+
+
+Wednesday
+
+09:00-10:00 - Automated testing
+10:15-11:30 - Modular code development
+
+
+11:30-12:15 - Lunch break
+12:15-14:00 - How to release and publish your code
+
+
+14:15-15:00 - Debriefing and Q&A
+
+Participants work on their projects
+Together we study actual codes that participants wrote or work on
+Constructively we discuss possible improvements
+Give individual feedback on code projects
+
+
+
+
+
+Thursday
+
+
+
+Software install instructions
+[this page is adapted from https://aaltoscicomp.github.io/python-for-scicomp/installation/ ]
+
+Choosing an installation method
+For this course we will install an isolated environment
+with following dependencies:
+
+
environment.yml requirements.txt
name : course
+channels :
+ - conda-forge
+ - bioconda
+dependencies :
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - jupyterlab
+ - pytest
+ - scalene
+ - flit
+ - ruff
+ - icecream
+ - snakemake-minimal
+ - myst-parser
+ - sphinx
+ - sphinx-rtd-theme
+ - sphinx-autoapi
+ - sphinx-autobuild
+ - black
+ - isort
+ - pip
+ - pip :
+ - jupyterlab-code-formatter
+
+
+
click
+numpy
+pandas
+scipy
+altair
+vl - convert - python
+jupyterlab
+pytest
+scalene
+flit
+ruff
+icecream
+myst - parser
+sphinx
+sphinx - rtd - theme
+sphinx - autoapi
+sphinx - autobuild
+black
+isort
+jupyterlab - code - formatter
+
+
+
+
+If you are new to Python or unsure how to create isolated environments in
+Python from files above, please follow the
+instructions below.
+
+
There are many choices and we try to suggest a good compromise
+
There are very many ways to install Python and packages with pros and cons and
+in addition there are several operating systems with their own quirks. This
+can be a huge challenge for beginners to navigate. It can also difficult for
+instructors to give recommendations for something which will work everywhere
+and which everybody will like.
+
Below we will recommend Miniforge since it is free, open source, general,
+available on all operating systems, and provides a good basis for reproducible
+environments. However, it does not provide a graphical user interface during
+installation. This means that every time we want to start a JupyterLab session,
+we will have to go through the command line.
+
+
+
Python, conda, anaconda, miniforge, etc?
+
Unfortunately there are many options and a lot of jargon.
+Here is a crash course:
+
+Python is a programming language very commonly used in
+science, it’s the topic of this course.
+Conda is a package manager: it allows distributing and
+installing packages, and is designed for complex scientific
+code.
+Mamba is a re-implementation of Conda to be much faster with
+resolving dependencies and installing things.
+An environment is a self-contained collections of packages
+which can be installed separately from others. They are used so
+each project can install what it needs without affecting others.
+Anaconda is a commercial distribution of Python+Conda+many
+packages that all work together. It used to be freely usable for
+research, but since ~2023-2024 it’s more limited. Thus, we don’t
+recommend it (even though it has a nice graphical user interface).
+conda-forge is another channel of distributing packages that
+is maintained by the community, and thus can be used by anyone.
+(Anaconda’s parent company also hosts conda-forge packages)
+Miniforge is a distribution of conda pre-configured for
+conda-forge. It operates via the command line.
+Miniconda is a distribution of conda pre-configured to use
+the Anaconda channels.
+
+
We will gain a better background and overview in the section
+Reproducible environments and dependencies .
+
+
+
+Installing Python via Miniforge
+Follow the instructions on the miniforge web page . This installs
+the base, and from here other packages can be installed.
+Unsure what to download and what to do with it?
+
+
Windows MacOS Linux
You want to download and run Miniforge3-Windows-x86_64.exe
.
+
You probably want to download the Miniforge3-MacOSX-arm64.sh
file (unless
+you don’t have an Arm processor) and then run it in the terminal with:
+
$ bash Miniforge3-MacOSX-arm64.sh
+
+
+
You probably want to download the Miniforge3-Linux-x86_64.sh
file (unless
+you don’t have an x86_64 processor) and then run it in the terminal with:
+
$ bash Miniforge3-Linux-x86_64.sh
+
+
+
+
+
+Installing and activating the software environment
+First we will start Python in a way that activates conda/mamba. Then we will
+install the software environment from this environment.yml
+file .
+An environment is a self-contained set of extra libraries - different
+projects can use different environments to not interfere with each other. This
+environment will have all of the software needed for this particular course.
+We will call the environment course
.
+
+
Windows Linux / MacOS
Use the “Miniforge Prompt” to start Miniforge. This
+will set up everything so that conda
and mamba
are
+available.
+Then type
+(without the $
):
+
$ mamba env create -n course -f https://raw.githubusercontent.com/coderefinery/reproducible-python-ml/main/software/environment.yml
+
+
+
Each time you start a new command line terminal,
+you can activate Miniforge by running
+(without the $
):
+
$ source ~/miniforge3/bin/activate
+
+
+
This is needed so that
+Miniforge is usable wherever you need, but doesn’t affect any
+other software on your computer (this is not needed if you
+choose “Do you wish to update your shell profile to
+automatically initialize conda?”, but then it will always be
+active).
+
In the second step, we will install the software environment:
+
$ mamba env create -n course -f https://raw.githubusercontent.com/coderefinery/reproducible-python-ml/main/software/environment.yml
+
+
+
+
+
+Starting JupyterLab
+Every time we want to start a JupyterLab session,
+we will have to go through the command line and first
+activate the course
environment.
+
+
Windows Linux / MacOS
Start the Miniforge Prompt. Then type
+(without the $
):
+
$ conda activate course
+$ jupyter-lab
+
+
+
Start the terminal and in the terminal, type
+(without the $
):
+
$ source ~/miniforge3/bin/activate
+$ conda activate course
+$ jupyter-lab
+
+
+
+
+
+Removing the software environment
+
+
Windows Linux / MacOS
In the Miniforge Prompt, type
+(without the $
):
+
$ conda env list
+$ conda env remove --name course
+$ conda env list
+
+
+
In the terminal, type
+(without the $
):
+
$ source ~/miniforge3/bin/activate
+$ conda env list
+$ conda env remove --name course
+$ conda env list
+
+
+
+
+
+How to verify your installation
+Start JupyterLab (as described above). It will hopefully open up your browser
+and look like this:
+
+
+
+
+JupyterLab opened in the browser. Click on the Python 3 tile.
+
+
+Once you clicked the Python 3 tile it should look like this:
+
+
+
+
+Python 3 notebook started.
+
+
+Into that blue “cell” please type the following:
+import altair
+import pandas
+
+print ( "all good - ready for the course" )
+
+
+
+
+
+
+Please copy these lines and click on the “play”/”run” icon.
+
+
+This is how it should look:
+
+
+
+
+Screenshot after successful import.
+
+
+If this worked, you are all set and can close JupyterLab (no need to save these
+changes).
+This is how it should not look:
+
+
+
+
+Error: required packages could not be found.
+
+
+
+
+
+
+
+Example project: 2D classification task using a nearest-neighbor predictor
+The example code
+that we will study is a relatively simple nearest-neighbor predictor written in
+Python. It is not important or expected that we understand the code in detail.
+The code will produce something like this:
+
+
+
+
+The bottom row shows the training data (two labels) and the top row shows the
+test data and whether the nearest-neighbor predictor classified their labels
+correctly.
+
+
+The big picture of the code is as follows:
+
+We can choose the number of samples (the example above has 50 samples).
+The code will generate samples with two labels (0 and 1) in a 2D space.
+One of the labels has a normal distribution and a circular distribution with
+some minimum and maximum radius.
+The second label only has a circular distribution with a different radius.
+Then we try to predict whether the test samples belong to label 0 or 1 based
+on the nearest neighbors in the training data. The number of neighbors can
+be adjusted and the code will take label of the majority of the neighbors.
+
+
+Example run
+
+
Instructor note
+
The instructor demonstrates running the code on their computer.
+
+The code is written to accept command-line arguments to specify the number
+of samples and file names. Later we will discuss advantages of this approach.
+Let us try to get the help text:
+$ python generate_data.py --help
+
+Usage: generate_data.py [OPTIONS]
+
+ Program that generates a set of training and test samples for a non-linear
+ classification task.
+
+Options:
+ --num-samples INTEGER Number of samples for each class. [required]
+ --training-data TEXT Training data is written to this file. [required]
+ --test-data TEXT Test data is written to this file. [required]
+ --help Show this message and exit.
+
+
+We first generate the training and test data:
+$ python generate_data.py --num-samples 50 --training-data train.csv --test-data test.csv
+
+Generated 50 training samples (train.csv) and test samples (test.csv).
+
+
+In a second step we generate predictions for the test data:
+$ python generate_predictions.py --num-neighbors 7 --training-data train.csv --test-data test.csv --predictions predictions.csv
+
+Predictions saved to predictions.csv
+
+
+Finally, we can plot the results:
+$ python plot_results.py --training-data train.csv --predictions predictions.csv --output-chart chart.svg
+
+Accuracy: 0.94
+Saved chart to chart.svg
+
+
+
+
+Discussion and goals
+
+
+
+
We will not focus on …
+
+… how the code works internally in detail.
+… whether this is the most efficient algorithm.
+… whether the code is numerically stable.
+… how to code scales with system size.
+… whether it is portable to other operating systems (we will discuss this later).
+
+
+
+
+
+Introduction to version control with Git and GitHub
+
+
+Motivation
+
+
Objectives
+
+Browse commits and branches of a Git repository.
+Remember that commits are like snapshots of the repository at a certain
+point in time.
+Know the difference between Git (something that tracks changes) and
+GitHub/GitLab (a web platform to host Git repositories).
+
+
+
+Why do we need to keep track of versions?
+Version control is an answer to the following questions (do you recognize some
+of them?):
+
+“It broke … hopefully I have a working version somewhere?”
+“Can you please send me the latest version?”
+“Where is the latest version?”
+“Which version are you using?”
+“Which version have the authors used in the paper I am trying to reproduce?”
+“Found a bug! Since when was it there?”
+“I am sure it used to work. When did it change?”
+“My laptop is gone. Is my thesis now gone?”
+
+
+
+Demonstration
+
+Example repository: https://github.com/workshop-material/classification-task
+Commits are like snapshots and if we break something we can go back to a
+previous snapshot.
+Commits carry metadata about changes: author, date, commit message, and
+a checksum.
+Branches are like parallel universes where you can experiment with
+changes without affecting the default branch:
+https://github.com/workshop-material/classification-task/network
+(“Insights” -> “Network”)
+With version control we can annotate code
+(example ).
+Collaboration : We can fork (make a copy on GitHub), clone (make a copy
+to our computer), review, compare, share, and discuss.
+Code review : Others can suggest changes using pull requests or merge
+requests. These can be reviewed and discussed before they are merged.
+Conceptually, they are similar to “suggesting changes” in Google Docs.
+
+
+
+
+
+What we typically like to snapshot
+
+Software (this is how it started but Git/GitHub can track a lot more)
+Scripts
+Documents (plain text files much better suitable than Word documents)
+Manuscripts (Git is great for collaborating/sharing LaTeX or Quarto manuscripts)
+Configuration files
+Website sources
+Data
+
+
+
Discussion
+
In this example somebody tried to keep track of versions without a version
+control system tool like Git. Discuss the following directory listing. What
+possible problems do you anticipate with this kind of “version control”:
+
myproject-2019.zip
+myproject-2020-february.zip
+myproject-2021-august.zip
+myproject-2023-09-19-working.zip
+myproject-2023-09-21.zip
+myproject-2023-09-21-test.zip
+myproject-2023-09-21-myversion.zip
+myproject-2023-09-21-newfeature.zip
+...
+(100 more files like these)
+
+
+
+
+
+
+
+Forking, cloning, and browsing
+In this episode, we will look at an existing repository to understand how
+all the pieces work together. Along the way, we will make a copy (by
+forking and/or cloning ) of the repository for us, which will be used
+for our own changes.
+
+
Objectives
+
+See a real Git repository and understand what is inside of it.
+Understand how version control allows advanced inspection of a
+repository.
+See how Git allows multiple people to work on the same project at the same time.
+See the big picture instead of remembering a bunch of commands.
+
+
+
+GitHub, VS Code, or command line
+We offer three different paths for this exercise:
+
+GitHub (this is the one we will demonstrate)
+VS Code (if you prefer to follow along using an editor)
+Command line (for people comfortable with the command line)
+
+
+
+Creating a copy of the repository by “forking” or “cloning”
+A repository is a collection of files in one directory tracked by Git. A
+GitHub repository is GitHub’s copy, which adds things like access control,
+issue tracking, and discussions. Each GitHub repository is owned by a user or
+organization, who controls access.
+First, we need to make our own copy of the exercise repository. This will
+become important later, when we make our own changes.
+
+
+
+
+Illustration of forking a repository on GitHub.
+
+
+
+
+
+
+Illustration of cloning a repository to your computer.
+
+
+
+
+
+
+It is also possible to do this: to clone a forked repository to your computer.
+
+
+At all times you should be aware of if you are looking at your repository
+or the upstream repository (original repository):
+
+
+
How to create a fork
+
+Go to the repository view on GitHub: https://github.com/workshop-material/classification-task
+First, on GitHub, click the button that says “Fork”. It is towards
+the top-right of the screen.
+You should shortly be redirected to your copy of the repository
+USER/classification-task .
+
+
+
+
Instructor note
+
Before starting the exercise session show
+how to fork the repository to own account
+(above).
+
+
+
+Exercise: Copy and browse an existing project
+Work on this by yourself or in pairs.
+
+
Exercise preparation
+
+
GitHub VS Code Command line
In this case you will work on a fork.
+
You only need to open your own view, as described above. The browser
+URL should look like https://github.com/USER /classification-task, where
+USER is your GitHub username.
+
You need to have forked the repository as described above.
+
We need to start by making a clone of this repository so that you can work
+locally.
+
+Start VS Code.
+If you don’t have the default view (you already have a project
+open), go to File → New Window.
+Under “Start” on the screen, select “Clone Git Repository…”. Alternatively
+navigate to the “Source Control” tab on the left sidebar and click on the “Clone Repository” button.
+Paste in this URL: https://github.com/USER/classification-task
, where
+USER
is your username. You can copy this from the browser.
+Browse and select the folder in which you want to clone the
+repository.
+Say yes, you want to open this repository.
+Select “Yes, I trust the authors” (the other option works too).
+
+
This path is advanced and we do not include all command line
+information: you need to be somewhat
+comfortable with the command line already.
+
You need to have forked the repository as described above.
+
We need to start by making a clone of this repository so that you can work
+locally.
+
+Start the terminal in which you use Git (terminal application, or
+Git Bash).
+Change to the directory where you would want the repository to be
+(cd ~/code
for example, if the ~/code
directory is where you
+store your files).
+Run the following command: git clone https://github.com/USER/classification-task
, where USER
is your
+username. You might need to use a SSH clone command instead of
+HTTPS, depending on your setup.
+Change to that directory: cd classification-task
+
+
+
+
+
Exercise: Browsing an existing project (20 min)
+
Browse the example project and
+explore commits and branches, either on a fork or on a clone. Take notes and
+prepare questions. The hints are for the GitHub path in the browser.
+
+Browse the commit history : Are commit messages understandable?
+(Hint: “Commit history”, the timeline symbol, above the file list)
+Compare the commit history with the network graph (“Insights” -> “Network”). Can you find the branches?
+Try to find the history of commits for a single file , e.g. generate_predictions.py
.
+(Hint: “History” button in the file view)
+Which files include the word “training” ?
+(Hint: the GitHub search on top of the repository view)
+In the generate_predictions.py
file,
+find out who modified the evaluation of “majority_index”
+last and in which commit .
+(Hint: “Blame” view in the file view)
+Can you use this code yourself? Are you allowed to share
+modifications ?
+(Hint: look for a license file)
+
+
+The solution below goes over most of the answers, and you are
+encouraged to use it when the hints aren’t enough - this is by
+design.
+
+
+Solution and walk-through
+
+(1) Basic browsing
+The most basic thing to look at is the history of commits.
+
+This is visible from a button in the repository view. We see every
+change, when, and who has committed.
+Every change has a unique identifier, such as 79ce3be
. This can
+be used to identify both this change, and the whole project’s
+version as of that change.
+Clicking on a change in the view shows more.
+
+
+
GitHub VS Code Command line
Click on the timeline symbol in the repository view:
+
+
+
+
+
This can be done from “Timeline”, in the bottom of explorer, but only
+for a single file.
+
+
+
+(2) Compare commit history with network graph
+The commit history we saw above looks linear: one commit after
+another. But if we look at the network view, we see some branching and merging points.
+We’ll see how to do these later. This is another one of the
+basic Git views.
+
+
GitHub VS Code Command line
In a new browser tab, open the “Insights” tab, and click on “Network”.
+You can hover over the commit dots to see the person who committed and
+how they correspond with the commits in the other view:
+
+
+
+
+
We don’t know how to do this without an extension. Try starting a terminal and using the
+“Command line” option.
+
This is a useful command to browse the network of commits locally:
+
$ git log --graph --oneline --decorate --all
+
+
+
To avoid having to type this long command every time, you can define an alias (shortcut):
+
$ git config --global alias.graph "log --graph --oneline --decorate --all"
+
+
+
From then on, you can use git graph
to see the network graph.
+
+
+
+(3) How can you browse the history of a single file?
+We see the history for the whole repository, but we can also see it
+for a single file.
+
+
GitHub VS Code Command line
Navigate to the file view: Main page → generate_predictions.py.
+Click the “History” button near the top right.
+
Open generate_predictions.py file in the editor. Under the file browser,
+we see a “Timeline” view there.
+
The git log
command can take a filename and provide the log of only
+a single file:
+
$ git log generate_predictions.py
+
+
+
+
+
+(4) Which files include the word “training”?
+Version control makes it very easy to find all occurrences of a
+word or pattern. This is useful for things like finding where functions or
+variables are defined or used.
+
+
GitHub VS Code Command line
We go to the main file view. We click the Search magnifying
+class at the very top, type “training”, and click enter. We see every
+instance, including the context.
+
+
Searching in a forked repository will not work instantaneously!
+
It usually takes a few minutes before one can search for keywords in a forked repository
+since it first needs to build the search index the very first time we search.
+Start it, continue with other steps, then come back to this.
+
+
If you use the “Search” magnifying class on the left sidebar, and
+search for “training” it shows the occurrences in every file. You can
+click to see the usage in context.
+
grep
is the command line tool that searches for lines matching a term
+
$ git grep training # only the lines
+$ git grep -C 3 training # three lines of context
+$ git grep -i training # case insensitive
+
+
+
+
+
+(5) Who modified a particular line last and when?
+This is called the “annotate” or “blame” view. The name “blame”
+is very unfortunate, but it is the standard term for historical reasons
+for this functionality and it is not meant to blame anyone.
+
+
GitHub VS Code Command line
From a file view, change preview to “Blame” towards the top-left.
+To get the actual commit, click on the commit message next
+to the code line that you are interested in.
+
This requires an extension. We recommend for now you use the command
+line version, after opening a terminal.
+
These two commands are similar but have slightly different output.
+
$ git annotate generate_predictions.py
+$ git blame generate_predictions.py
+
+
+
+
+
+(6) Can you use this code yourself? Are you allowed to share modifications?
+
+Look at the file LICENSE
.
+On GitHub, click on the file to see a nice summary of what we can do with this:
+
+
+
+
+
+
+
+
+Summary
+
+Git allowed us to understand this simple project much better than we
+could, if it was just a few files on our own computer.
+It was easy to share the project with the course.
+By forking the repository, we created our own copy. This is
+important for the following, where we will make changes to
+our copy.
+
+
+
+
+
+Creating branches and commits
+The first and most basic task to do in Git is record changes using
+commits. In this part, we will record changes in two
+ways: on a new branch (which supports multiple lines of work at once), and directly
+on the “main” branch (which happens to be the default branch here).
+
+
Objectives
+
+Record new changes to our own copy of the project.
+Understand adding changes in two separate branches.
+See how to compare different versions or branches.
+
+
+
+Background
+
+In the previous episode we have browsed an existing repository and saw commits
+and branches .
+Each commit is a snapshot of the entire project at a certain
+point in time and has a unique identifier (hash ) .
+A branch is a line of development, and the main
branch or master
branch
+are often the default branch in Git.
+A branch in Git is like a sticky note that is attached to a commit . When we add
+new commits to a branch, the sticky note moves to the new commit.
+Tags are a way to mark a specific commit as important, for example a release
+version. They are also like a sticky note, but they don’t move when new
+commits are added.
+
+
+
+
+
+What if two people, at the same time, make two different changes? Git
+can merge them together easily. Image created using https://gopherize.me/
+(inspiration ).
+
+
+
+
+Exercise: Creating branches and commits
+
+
+
+
+Illustration of what we want to achieve in this exercise.
+
+
+
+
Exercise: Practice creating commits and branches (20 min)
+
+First create a new branch and then either add a new file or modify an
+existing file and commit the change. Make sure that you now work on your
+copy of the example repository. In your new commit you can share a Git or
+programming trick you like or improve the documentation.
+In a new commit, modify the file again.
+Switch to the main
branch and create a commit there.
+Browse the network and locate the commits that you just created (“Insights” -> “Network”).
+Compare the branch that you created with the main
branch. Can you find an easy way to see the differences?
+Can you find a way to compare versions between two arbitrary commits in the repository?
+Try to rename the branch that you created and then browse the network again.
+Try to create a tag for one of the commits that you created (on GitHub,
+create a “release”).
+Optional: If this was too easy, you can try to create a new branch and on
+this branch work on one of these new features:
+
+The random seed is now set to a specific number (42). Make it possible to
+set the seed to another number or to turn off the seed setting via the command line interface.
+Move the code that does the majority vote to an own function.
+Write a test for the new majority vote function.
+The num_neighbors
in the code needs to be odd. Change the code so that it stops with an error message if an even number is given.
+Add type annotations to some functions.
+When calling the scatter_plot
function, call the function with named arguments.
+Add example usage to README.md.
+Add a Jupyter Notebook version of the example code.
+
+
+
+
+The solution below goes over most of the answers, and you are
+encouraged to use it when the hints aren’t enough - this is by
+design.
+
+
+Solution and walk-through
+
+(1) Create a new branch and a new commit
+
+
GitHub VS Code Command line
+Where it says “main” at the top left, click, enter a new branch
+name (e.g. new-tutorial
), then click on
+“Create branch … from main”.
+Make sure you are still on the new-tutorial
branch (it should say
+it at the top), and click “Add file” → “Create new file” from the
+upper right.
+Enter a filename where it says “Name your file…”.
+Share some Git or programming trick you like.
+Click “Commit changes”
+Enter a commit message. Then click “Commit
+changes”.
+
+
You should appear back at the file browser view, and see your
+modification there.
+
+Make sure that you are on the main branch.
+Version control button on left sidebar → Three dots in upper right of source control → Branch → Create branch.
+VS Code automatically switches to the new branch.
+Create a new file.
+In the version control sidebar, click the +
sign to add the file for the next commit.
+Enter a brief message and click “Commit”.
+
+
Create a new branch called new-tutorial
from main
and switch to it:
+
$ git switch --create new-tutorial main
+
+
+
Then create the new file. Finally add and commit the file:
+
$ git add tutorial.md # or a different file name
+$ git commit -m "sharing a programming trick"
+
+
+
+
+
+(2) Modify the file again with a new commit
+
+
GitHub VS Code Command line
This is similar to before, but we click on the existing file to
+modify.
+
+Click on the file you added or modified previously.
+Click the edit button, the pencil icon at top-right.
+Follow the “Commit changes” instructions as in the previous step.
+
+
Repeat as in the previous step.
+
Modify the file. Then commit the new change:
+
$ git add tutorial.md
+$ git commit -m "short summary of the change"
+
+
+
Make sure to replace “short summary of the change” with a meaningful commit
+message.
+
+
+
+(3) Switch to the main branch and create a commit there
+
+
GitHub VS Code Command line
+Go back to the main repository page (your user’s page).
+In the branch switch view (top left above the file view), switch to
+main
.
+Modify another file that already exists, following the pattern
+from above.
+
+
Use the branch selector at the bottom to switch back to the main branch. Repeat the same steps as above,
+but this time modify a different file.
+
First switch to the main
branch:
+
+
Then modify a file. Finally git add
and then commit the change:
+
$ git commit -m "short summary of the change"
+
+
+
+
+
+(4) Browse the commits you just made
+Let’s look at what we did. Now, the main
and the new branches
+have diverged: both have some modifications. Try to find the commits
+you created.
+
+
GitHub VS Code Command line
Insights tab → Network view (just like we have done before).
+
This requires an extension. Opening the VS Code terminal lets you use the
+command line method (View → Terminal will open a terminal at bottom). This is
+a normal command line interface and very useful for work.
+
$ git graph
+$ git log --graph --oneline --decorate --all # if you didn't define git graph yet.
+
+
+
+
+
+(5) Compare the branches
+Comparing changes is an important thing we need to do. When using the
+GitHub view only, this may not be so common, but we’ll show it so that
+it makes sense later on.
+
+
GitHub VS Code Command line
A nice way to compare branches is to add /compare
to the URL of the repository,
+for example (replace USER):
+https://github.com/USER /classification-task/compare
+
This seems to require an extension. We recommend you use the command line method.
+
$ git diff main new-tutorial
+
+
+
Try also the other way around:
+
$ git diff new-tutorial main
+
+
+
Try also this if you only want to see the file names that are different:
+
$ git diff --name-only main new-tutorial
+
+
+
+
+
+(6) Compare two arbitrary commits
+This is similar to above, but not only between branches.
+
+
GitHub VS Code Command line
Following the /compare
-trick above, one can compare commits on GitHub by
+adjusting the following URL:
+https://github.com/USER /classification-task/compare/VERSION1 ..VERSION2
+
Replace USER
with your username and VERSION1
and VERSION2
with a commit hash or branch name.
+Please try it out.
+
Again, we recommend using the Command Line method.
+
First try this to get a short overview of the commits:
+
+
Then try to compare any two commit identifiers with git diff
.
+
+
+
+(7) Renaming a branch
+
+
GitHub VS Code Command line
Branch button → View all branches → three dots at right side → Rename branch.
+
Version control sidebar → Three dots (same as in step 2) → Branch → Rename branch. Make sure you are on the right branch before you start.
+
Renaming the current branch:
+
$ git branch -m new-branch-name
+
+
+
Renaming a different branch:
+
$ git branch -m different-branch new-branch-name
+
+
+
+
+
+(8) Creating a tag
+Tags are a way to mark a specific commit as important, for example a release
+version. They are also like a sticky note, but they don’t move when new
+commits are added.
+
+
GitHub VS Code Command line
On the right side, below “Releases”, click on “Create a new release”.
+
What GitHub calls releases are actually tags in Git with additional metadata.
+For the purpose of this exercise we can use them interchangeably.
+
Version control sidebar → Three dots (same as in step 2) → Tags → Create tag. Make sure you are on the expected commit before you do this.
+
Creating a tag:
+
$ git tag -a v1.0 -m "New manuscript version for the pre-print"
+
+
+
+
+
+
+Summary
+In this part, we saw how we can make changes to our files. With branches, we
+can track several lines of work at once, and can compare their differences.
+
+You could commit directly to main
if there is only one single line
+of work and it’s only you.
+You could commit to branches if there are multiple lines of work at
+once, and you don’t want them to interfere with each other.
+Tags are useful to mark a specific commit as important, for example a
+release version.
+In Git, commits form a so-called “graph”. Branches are tags in Git function
+like sticky notes that stick to specific commits. What this means for us is
+that it does not cost any significant disk space to create new branches.
+Not all files should be added to Git. For example, temporary files or
+files with sensitive information or files which are generated as part of
+the build process should not be added to Git. For this we use
+.gitignore
(more about this later: Practical advice: How much Git is necessary? ).
+Unsure on which branch you are or what state the repository is in?
+On the command line, use git status
frequently to get a quick overview.
+
+
+
+
+Merging changes and contributing to the project
+Git allows us to have different development lines where we can try things out.
+It also allows different people to work on the same project at the same. This
+means that we have to somehow combine the changes later. In this part we will
+practice this: merging .
+
+
Objectives
+
+Understand that on GitHub merging is done through a pull request (on GitLab: “merge request”). Think
+of it as a change proposal .
+Create and merge a pull request within your own repository.
+Understand (and optionally) do the same across repositories, to contribute to
+the upstream public repository.
+
+
+
+Exercise
+
+
+
+
+Illustration of what we want to achieve in this exercise.
+
+
+
+
Exercise: Merging branches
+
+
GitHub Local (VS Code, Command line)
First, we make something called a pull request, which allows
+review and commenting before the actual merge.
+
We assume that in the previous exercise you have created a new branch with one
+or few new commits. We provide basic hints. You should refer to the solution
+as needed.
+
+Navigate to your branch from the previous episode
+(hint: the same branch view we used last time).
+Begin the pull request process
+(hint: There is a “Contribute” button in the branch view).
+Add or modify the pull request title and description, and verify the other data.
+In the pull request verify the target repository and the target
+branch. Make sure that you are merging within your own repository.
+GitHub: By default, it will offer to make the change to the
+upstream repository, workshop-material
. You should change this , you
+shouldn’t contribute your commit(s) upstream yet. Where it says
+base repository
, select your own repository.
+Create the pull request by clicking “Create pull request”. Browse
+the network view to see if anything has changed yet.
+Merge the pull request, or if you are not on GitHub you can merge
+the branch locally. Browse the network again. What has changed?
+Find out which branches are merged and thus safe to delete. Then remove them
+and verify that the commits are still there, only the branch labels are
+gone (hint: you can delete branches that have been merged into main
).
+Optional: Try to create a new branch with a new change, then open a pull
+request but towards the original (upstream) repository. We will later merge few of
+those.
+
+
When working locally, it’s easier to merge branches: we can just do
+the merge, without making a pull request. But we don’t have that step
+of review and commenting and possibly adjusting.
+
+Switch to the main
branch that you want to merge the other
+branch into. (Note that this is the other way around from the
+GitHub path).
+
+
Then:
+
+Merge the other branch into main
(which is then your current branch).
+Find out which branches are merged and thus safe to delete. Then remove them
+and verify that the commits are still there, only the branch labels are
+gone. (Hint: you can delete branches that have been merged into main
).
+Optional: Try to create a new branch, and make a
+GitHub pull request with your change, and contribute it to our
+upstream repository.
+
+
+
+The solution below goes over most of the answers, and you are
+encouraged to use it when the hints aren’t enough - this is by design.
+
+
+Solution and walk-through
+
+(1) Navigate to your branch
+Before making the pull request, or doing a merge, it’s important to
+make sure that you are on the right branch. Many people have been
+frustrated because they forgot this!
+
+
GitHub VS Code Command line
GitHub will notice a recently changed branch and offer to make a pull request (clicking there will bring you to step 3):
+
+
+
+
+
If the yellow box is not there, make sure you are on the branch you want to
+merge from :
+
+
+
+
+
Remember, you need to switch to the main
branch, the branch we want to merge to .
+This is different from the GitHub path.
+
Remember, you need to switch to the main
branch, the branch we want to merge to .
+This is different from the GitHub path:
+
+
+
+
+(2) Begin the pull request process
+In GitHub, the pull request is the way we propose to merge two
+branches together. We start the process of making one.
+
+
GitHub VS Code Command line
+
+
+
+
It is possible to open pull requests from the editor, but
+we don’t cover that here.
+
If you are working locally, continue to step 5.
+
It is possible to open pull requests from the command line, but
+we don’t cover that here.
+
If you are working locally, continue to step 5.
+
+
+
+(3) Fill out and verify the pull request
+Check that the pull request is directed to the right repository and branch
+and that it contains the changes that you meant to merge.
+
+
GitHub VS Code Command line
Things to check:
+
+Base repository: this should be your own
+Title: make it descriptive
+Description: make it informative
+Scroll down to see commits: are these the ones you want to merge?
+Scroll down to see the changes: are these the ones you want to merge?
+
+
+
+
+This screenshot only shows the top part. If you scroll down, you
+can see the commits and the changes. We recommend to do this before
+clicking on “Create pull request”.
+
+
+
+
+
If you are working locally, continue to step 5.
+
If you are working locally, continue to step 5.
+
+
+
+(4) Create the pull request
+We actually create the pull request. Don’t forget to navigate to the Network
+view after opening the pull request. Note that the changes proposed in the
+pull request are not yet merged.
+
+
GitHub VS Code Command line
Click on the green button “Create pull request”.
+
If you click on the little arrow next to “Create pull request”, you can also
+see the option to “Create draft pull request”. This will be interesting later
+when collaborating with others. It allows you to open a pull request that is
+not ready to be merged yet, but you want to show it to others and get feedback.
+
It is possible to create pull requests from the editor, but
+we don’t cover that here.
+
If you are working locally, continue to step 5.
+
It is possible to create pull requests from the command line, but
+we don’t cover that here.
+
If you are working locally, continue to step 5.
+
+
+
+(5) Merge the pull request
+Now, we do the actual merging. We see some effects now.
+
+
GitHub VS Code Command line
Review it again (commits and changes), and then click “Merge pull request”.
+
After merging, verify the network view. Also navigate then to your “main”
+branch and check that your change is there.
+
Just like with the command line, when we merge we modify our current branch. Verify you are on the main
branch.
+
+Verify current branch at the bottom.
+From the version control sidebar → Three dots → Branch → Merge.
+In the selector that comes up, choose the branch you want to merge from .
+The commits on that branch will be added to the current branch.
+
+
On the command line, when we merge, we always modify our current branch.
+
If you are not sure anymore what your current branch is, type:
+
+
… or equally useful to see where we are right now:
+
+
In this case we merge the new-tutorial
branch into our current branch:
+
$ git merge new-tutorial
+
+
+
+
+
+(6) Delete merged branches
+Before deleting branches, first check whether they are merged.
+If you delete an un-merged branch, it will be difficult to find the commits
+that were on that branch. If you delete a merged branch, the commits are now
+also part of the branch where we have merged to.
+
+
GitHub VS Code Command line
One way to delete the branch is to click on the “Delete branch” button after the pull
+request is merged:
+
+
+
+
+
But what if we forgot? Then navigate to the branch view:
+
+
+
+
+
In the overview we can see that it has been merged and we can delete it.
+
From the Source Control sidebar → the three dots (as before) → Branch → Delete Branch. Select the branch name to delete.
+
Verify which branches are merged to the current branch:
+
$ git branch --merged
+
+* main
+ new-tutorial
+
+
+
This means that it is safe to delete the new-tutorial
branch:
+
$ git branch -d new-tutorial
+
+
+
Verify then that the branch is gone but that the commits are still there:
+
$ git branch
+$ git log --oneline
+
+
+
+
+
+(7) Contribute to the original repository with a pull request
+This is an advanced step. We will practice this tomorrow and
+it is OK to skip this at this stage.
+
+
GitHub VS Code Command line
Now that you know how to create branches and opening a pull request, try to
+open a new pull request with a new change but this time the base repository
+should be the upstream one.
+
In other words, you now send a pull request across repositories : from your fork
+to the original repository.
+
Another thing that is different now is that you might not have permissions to
+merge the pull request. We can then together review and browse the pull
+request.
+
Not described. We will return to this when we discuss collaboration using GitHub.
+
You would create a new branch locally like above, push it to GitHub to your own
+user’s repository, and from there open a pull request towards the upstream
+repository.
+
Not described. We will return to this when we discuss collaboration using GitHub.
+
You would create a new branch locally like above, push it to GitHub to your own
+user’s repository, and from there open a pull request towards the upstream
+repository.
+
+
+
+
+Summary
+
+We learned how to merge two branches together.
+When is this useful? This is not only useful to combine development lines in
+your own work. Being able to merge branches also forms the basis for collaboration .
+Branches which are merged to other branches are safe to delete, since we only
+delete the “sticky note” next to a commit, not the commits themselves.
+
+
+
+
+Conflict resolution
+
+Resolving a conflict (demonstration)
+A conflict is when Git asks humans to decide during a merge which of two
+changes to keep if the same portion of a file has been changed in two
+different ways on two different branches .
+We will practice conflict resolution in the collaborative Git lesson (next
+day).
+Here we will only demonstrate how to create a conflict and how to resolve it,
+all on GitHub . Once we understand how this works, we will be more confident
+to resolve conflicts also in the command line (we can demonstrate this if
+we have time).
+How to create a conflict (please try this in your own time and just watch now ):
+
+Create a new branch from main
and on it make a change to a file.
+On main
, make a different change to the same part of the same file.
+Now try to merge the new branch to main
. You will get a conflict.
+
+How to resolve conflicts:
+
+On GitHub, you can resolve conflicts by clicking on the “Resolve conflicts”
+button. This will open a text editor where you can choose which changes to
+keep.
+Make sure to remove the conflict markers.
+After resolving the conflict, you can commit the changes and merge the
+pull request.
+Sometimes a conflict is between your change and somebody else’s change. In
+that case, you might have to discuss with the other person which changes to
+keep.
+
+
+
+Avoiding conflicts
+
+
The human side of conflicts
+
+What does it mean if two people do the same thing in two different ways?
+What if you work on the same file but do two different things in the different sections?
+What if you do something, don’t tell someone from 6 months, and then try to combine it with other people’s work?
+How are conflicts avoided in other work? (Only one person working at once?
+Declaring what you are doing before you start, if there is any chance someone
+else might do the same thing, helps.)
+
+
+
+Human measures
+
+
+Collaboration measures
+
+
+Project layout measures
+
+
+Technical measures
+
+Share your changes early and often : This is one of the happy,
+rare circumstances when everyone doing the selfish thing (publishing your
+changes as early as practical) results in best case for everyone!
+Pull/rebase often to keep up to date with upstream.
+Resolve conflicts early.
+
+
+
+
+
+
+Practical advice: How much Git is necessary?
+
+Writing useful commit messages
+Useful commit messages summarize the change and provide context .
+If you need a commit message that is longer than one line,
+then the convention is: one line summarizing the commit, then one empty line,
+then paragraph(s) with more details in free form, if necessary.
+Good example:
+increase alpha to 2.0 for faster convergence
+
+the motivation for this change is
+to enable ...
+...
+(more context)
+...
+this is based on a discussion in #123
+
+
+
+Why something was changed is more important than what has changed.
+Cross-reference to issues and discussions if possible/relevant.
+Bad commit messages: “fix”, “oops”, “save work”
+Just for fun, a page collecting bad examples: http://whatthecommit.com
+Write commit messages that will be understood
+15 years from now by someone else than you. Or by your future you.
+Many projects start out as projects “just for me” and end up to be successful projects
+that are developed by 50 people over decades.
+Commits with multiple authors are possible.
+
+Good references:
+
+
+
Note
+
A great way to learn how to write commit messages and to get inspired by their
+style choices: browse repositories of codes that you use/like :
+
Some examples (but there are so many good examples):
+
+
When designing commit message styles consider also these:
+
+How will you easily generate a changelog or release notes?
+During code review, you can help each other improving commit messages.
+
+
+But remember: it is better to make any commit, than no commit. Especially in small projects.
+Let not the perfect be the enemy of the good enough .
+
+
+What level of branching complexity is necessary for each project?
+Simple personal projects :
+
+Typically start with just the main
branch.
+Use branches for unfinished/untested ideas.
+Use branches when you are not sure about a change.
+Use tags to mark important milestones.
+If you are unsure what to do with unfinished and not working code, commit it
+to a branch.
+
+Projects with few persons: you accept things breaking sometimes
+
+Projects with few persons: changes are reviewed by others
+
+You create new feature branches for changes.
+Changes are reviewed before they are merged to the main
branch.
+Consider to write-protect the main
branch so that it can only be changed
+with pull requests or merge requests.
+
+
+
+How large should a commit be?
+
+Better too small than too large (easier to combine than to split).
+Often I make a commit at the end of the day (this is a unit I would not like to lose).
+Smaller sized commits may be easier to review for others than huge commits.
+A commit should not contain unrelated changes to simplify review and possible
+repair/adjustments/undo later (but again: imperfect commits are better than no commits).
+Imperfect commits are better than no commits.
+
+
+
+Working on the command line? Use “git status” all the time
+The git status
command is one of the most useful commands in Git
+to inform about which branch we are on, what we are about to commit,
+which files might not be tracked, etc.
+
+
+How about staging and committing?
+
+Commit early and often: rather create too many commits than too few.
+You can always combine commits later.
+Once you commit, it is very, very hard to really lose your code.
+Always fully commit (or stash) before you do dangerous things, so that you know you are safe.
+Otherwise it can be hard to recover.
+Later you can start using the staging area (where you first stage and then commit in a second step).
+Later start using git add -p
and/or git commit -p
.
+
+
+
+What to avoid
+
+Committing generated files/directories (example: __pycache__
, *.pyc
) ->
+use .gitignore
+files
+(collection of .gitignore templates ).
+Committing huge files -> use code review to detect this.
+Committing unrelated changes together.
+Postponing commits because the changes are “unfinished”/”ugly” -> better ugly
+commits than no commits.
+When working with branches:
+
+Working on unrelated things on the same branch.
+Not updating your branch before starting new work.
+Too ambitious branch which risks to never get completed.
+Over-engineering the branch layout and safeguards in small projects -> can turn people away from your project.
+
+
+
+
+
+
+Optional: How to turn your project to a Git repo and share it
+
+
Objectives
+
+Turn our own coding project (small or large, finished or unfinished) into a
+Git repository.
+Be able to share a repository on the web to have a backup or so that others
+can reuse and collaborate or even just find it.
+
+
+
+Exercise
+
+
+
+
+From a bunch of files to a local repository which we then share on GitHub.
+
+
+
+
Exercise: Turn your project to a Git repo and share it (20 min)
+
+Create a new directory called myproject (or a different name) with one or few files in it.
+This represents our own project. It is not yet a Git repository. You can try
+that with your own project or use a simple placeholder example.
+Turn this new directory into a Git repository.
+Share this repository on GitHub (or GitLab, since it really works the same).
+
+
We offer three different paths of how to do this exercise.
+
+Via GitHub web interface : easy and can be a good starting point if you are completely
+new to Git.
+VS Code is quite easy, since VS Code can offer to create the
+GitHub repositories for you.
+Command line : you need to create the
+repository on GitHub and link it yourself.
+
+
+
Only using GitHub VS Code Command line RStudio
Create an repository on GitHub
+
First log into GitHub, then follow the screenshots and descriptions below.
+
+
+
+
+Click on the “plus” symbol on top right, then on “New repository”.
+
+
+
Then:
+
+
+
+
+Choose a repository name, add a short description, and in this case make sure to check “Add a
+README file”. Finally “Create repository”.
+
+
+
Upload your files
+
Now that the repository is created, you can upload your files:
+
+
+
+
+Click on the “+” symbol and then on “Upload files”.
+
+
+
In VS Code it only takes few clicks.
+
First, open the folder in VS Code. My example project here contains two files.
+Then click on the source control icon:
+
+
+
+
+Open the folder in VS Code. Then click on the source control icon.
+
+
+
+
+
+
+Choose “Publish to GitHub”. In this case I never even clicked on “Initialize Repository”.
+
+
+
+
+
+
+In my case I chose to “Publish to GitHub public repository”. Here you can also rename
+the repository if needed.
+
+
+
+
+
+
+First time you do this you might need to authorize VS Code to access your
+GitHub account by redirecting you to https://github.com/login/oauth/authorize.
+
+
+
+
+
+
+After it is published, click on “Open on GitHub”.
+
+
+
Put your project under version control
+
My example project here consists of two files. Replace this with your own
+example files:
+
$ ls -l
+
+.rw------- 19k user 7 Mar 17:36 LICENSE
+.rw------- 21 user 7 Mar 17:36 myscript.py
+
+
+
I will first initialize a Git repository in this directory.
+If you get an error, try without the -b main
(and your default branch will
+then be called master
, this will happen for Git versions older than
+2.28):
+
+
Now add and commit the two files to the Git repository:
+
$ git add LICENSE myscript.py
+$ git commit -m "putting my project under version control"
+
+
+
If you want to add all files in one go, you can use git add .
instead of git add LICENSE myscript.py
.
+
Now you have a Git repository with one commit. Verify this with git log
.
+But it’s still only on your computer. Let’s put it on GitHub next.
+
Create an empty repository on GitHub
+
First log into GitHub, then follow the screenshots and descriptions below.
+
+
+
+
+Click on the “plus” symbol on top right, then on “New repository”.
+
+
+
Then create an empty repository without any files and without any commits:
+
+
+
+
+Choose a repository name, add a short description, but please do not check “Add a
+README file”. For “Add .gitignore” and “Choose a license” also leave as “None”. Finally “Create repository”.
+
+
+
Once you click the green “Create repository”, you will see a page similar to:
+
+
+
+
+
What this means is that we have now an empty project with either an HTTPS or an
+SSH address: click on the HTTPS and SSH buttons to see what happens.
+
Push an existing repository from your computer to GitHub
+
We now want to follow the “… or push an existing repository from the command line ”:
+
+In your terminal make sure you are still in your myproject directory.
+Copy paste the three lines below the red arrow to the terminal and execute
+those, in my case (you need to replace the “USER” part and possibly also
+the repository name ):
+
+
+
SSH HTTPS
$ git remote add origin git@github.com:USER/myproject.git
+
+
+
$ git remote add origin https://github.com/USER/myproject.git
+
+
+
+
Then:
+
$ git branch -M main
+$ git push -u origin main
+
+
+
The meaning of the above lines:
+
+Add a remote reference with the name “origin”
+Rename current branch to “main”
+Push branch “main” to “origin”
+
+
You should now see:
+
Enumerating objects: 4, done.
+Counting objects: 100% (4/4), done.
+Delta compression using up to 12 threads
+Compressing objects: 100% (3/3), done.
+Writing objects: 100% (4/4), 6.08 KiB | 6.08 MiB/s, done.
+Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
+To github.com:USER/myproject.git
+ * [new branch] main -> main
+branch 'main' set up to track 'origin/main'.
+
+
+
Reload your GitHub project website and your commits should now be
+online!
+
Troubleshooting
+
error: remote origin already exists
+
+
remote contains work that you do not have
+
+
This is not fully explained, because a lot of it is similar to the “Command
+line” method (and an RStudio expert could help us some). The main differences
+are:
+
Put your project under version control
+
+Tools → Version control → Project setup → Version control system = Git.
+Select “Yes” for “do you want to initialize a new git repository for this project.
+Select yes to restart the project with RStudio.
+Switch to branch main
to have you branch named that.
+
+
Create an empty repository on GitHub
+
Same as command line
+
Push an existing repository from your computer to GitHub
+
+Under the “Create new branch” button → “Add Remote”
+Remote name: origin
+Remote URL: as in command line (remember to select SSH or HTTPS as you have configured your RStudio)
+The “Push” (up arrow) button will send changes on your current branch to the remote. The “Pull” (down arrow) will get changes from the remote.
+
+
Troubleshooting
+
Same as command line
+
+
+
+
+Is putting software on GitHub/GitLab/… publishing?
+It is a good first step but to make your code truly findable and
+accessible , consider making your code citable and persistent : Get a
+persistent identifier (PID) such as DOI in addition to sharing the code
+publicly, by using services like Zenodo or similar
+services.
+More about this in How to publish your code .
+
+
+
+
+
+Where to start with documentation
+
+
Objectives
+
+Discuss what makes good documentation.
+Improve the README of your project or our example project.
+Explore Sphinx which is a popular tool to build documentation websites.
+Learn how to leverage GitHub Actions and GitHub Pages to build and deploy documentation.
+
+
+
+
+Why? 💗✉️ to your future self
+
+
+
+In-code documentation
+Not very useful (more commentary than comment):
+# now we check if temperature is below -50
+if temperature < - 50 :
+ print ( "ERROR: temperature is too low" )
+
+
+More useful (explaining why ):
+# we regard temperatures below -50 degrees as measurement errors
+if temperature < - 50 :
+ print ( "ERROR: temperature is too low" )
+
+
+Keeping zombie code “just in case” (rather use version control):
+# do not run this code!
+# if temperature > 0:
+# print("It is warm")
+
+
+Emulating version control:
+# John Doe: threshold changed from 0 to 15 on August 5, 2013
+if temperature > 15 :
+ print ( "It is warm" )
+
+
+
+
+Many languages allow “docstrings”
+Example (Python):
+def kelvin_to_celsius ( temp_k : float ) -> float :
+ """
+ Converts temperature in Kelvin to Celsius.
+
+ Parameters
+ ----------
+ temp_k : float
+ temperature in Kelvin
+
+ Returns
+ -------
+ temp_c : float
+ temperature in Celsius
+ """
+ assert temp_k >= 0.0 , "ERROR: negative T_K"
+
+ temp_c = temp_k - 273.15
+
+ return temp_c
+
+
+
+
Keypoints
+
+Documentation which is only in the source code is not enough.
+Often a README is enough.
+Documentation needs to be kept
+in the same Git repository as the code since we want it to evolve with
+the code.
+
+
+
+
+Often a README is enough - checklist
+
+Purpose
+Requirements
+Installation instructions
+Copy-paste-able example to get started
+Tutorials covering key functionality
+Reference documentation (e.g. API) covering all functionality
+Authors and recommended citation
+License
+Contribution guide
+
+See also the
+JOSS review checklist .
+
+
+Diátaxis
+Diátaxis is a systematic approach to technical documentation authoring.
+
+
+
+What if you need more than a README?
+
+
+
+Exercise: Set up a Sphinx documentation
+
+
Preparation
+
In this episode we will use the following 5 packages which we installed
+previously as part of the Software install instructions :
+
myst - parser
+sphinx
+sphinx - rtd - theme
+sphinx - autoapi
+sphinx - autobuild
+
+
+
Which repository to use? You have 3 options:
+
+Clone your fork of the example repository.
+If you don’t have that, you can clone the exercise repository itself.
+You can try this with your own project and the project does not have to
+be a Python project.
+
+
+There are at least two ways to get started with Sphinx:
+
+Use sphinx-quickstart
to create a new Sphinx project.
+This is what we will do instead : Create three files (doc/conf.py
, doc/index.md
, and doc/about.md
)
+as starting point and improve from there.
+
+
+
Exercise: Set up a Sphinx documentation
+
+Create the following three files in your project:
+ your-project/
+├── doc/
+│ ├── conf.py
+│ ├── index.md
+│ └── about.md
+└── ...
+
+
+This is conf.py
:
+project = "your-project"
+copyright = "2025, Authors"
+author = "Authors"
+release = "0.1"
+
+exclude_patterns = [ "_build" , "Thumbs.db" , ".DS_Store" ]
+
+extensions = [
+ "myst_parser" , # in order to use markdown
+]
+
+myst_enable_extensions = [
+ "colon_fence" , # ::: can be used instead of ``` for better rendering
+]
+
+html_theme = "sphinx_rtd_theme"
+
+
+This is index.md
(feel free to change the example text):
+# Our code documentation
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
+incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
+nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
+culpa qui officia deserunt mollit anim id est laborum.
+
+:::{toctree}
+:maxdepth: 2
+:caption: Some caption
+
+about.md
+:::
+
+
+This is about.md
(feel free to adjust):
+# About this code
+
+Work in progress ...
+
+
+
+Run sphinx-build
to build the HTML documentation:
+$ sphinx-build doc _build
+
+... lots of output ...
+The HTML pages are in _build.
+
+
+
+Try to open _build/index.html
in your browser.
+Experiment with adding more content, images, equations, code blocks, …
+
+
+
+
+There is a lot more you can do:
+
+This is useful if you want to check the integrity of all internal and external links:
+$ sphinx-build doc -W -b linkcheck _build
+
+
+
+sphinx-autobuild
+provides a local web server that will automatically refresh your view
+every time you save a file - which makes writing with live-preview much easier.
+
+
+
+Demo: Building documentation with GitHub Actions
+
+First we need to extend the environment.yml
file to include the necessary packages:
+name : classification-task
+channels :
+ - conda-forge
+dependencies :
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - myst-parser
+ - sphinx
+ - sphinx-rtd-theme
+ - sphinx-autoapi
+
+
+Then we add a GitHub Actions workflow .github/workflow/sphinx.yml
to build the documentation:
+name : Build documentation
+
+on :
+ push :
+ branches : [ main ]
+ pull_request :
+ branches : [ main ]
+
+permissions :
+ contents : write
+
+jobs :
+ docs :
+ runs-on : ubuntu-24.04
+
+ steps :
+ - name : Checkout
+ uses : actions/checkout@v4
+
+ - uses : mamba-org/setup-micromamba@v1
+ with :
+ micromamba-version : '2.0.5-0' # any version from https://github.com/mamba-org/micromamba-releases
+ environment-file : environment.yml
+ init-shell : bash
+ cache-environment : true
+ post-cleanup : 'all'
+ generate-run-shell : false
+
+ - name : Sphinx build
+ run : |
+ sphinx-build doc _build
+ shell : bash -el {0}
+
+ - name : Deploy to GitHub Pages
+ uses : peaceiris/actions-gh-pages@v4
+ if : ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
+ with :
+ publish_branch : gh-pages
+ github_token : ${{ secrets.GITHUB_TOKEN }}
+ publish_dir : _build/
+ force_orphan : true
+
+
+Now:
+
+Add these two changes to the GitHub repository.
+Go to “Settings” -> “Pages” -> “Branch” -> gh-pages
-> “Save”.
+Look at “Actions” tab and observe the workflow running and hopefully
+deploying the website.
+Finally visit the generated site. You can find it by clicking the About wheel
+icon on top right of your repository. There, select “Use your GitHub Pages
+website”.
+This is how we build almost all of our lesson websites ,
+including this one!
+Another popular place to deploy Sphinx documentation is ReadTheDocs .
+
+
+
+Optional: How to auto-generate API documentation in Python
+Add three tiny modifications (highlighted) to doc/conf.py
to auto-generate API documentation
+(this requires the sphinx-autoapi
package):
+project = "your-project"
+copyright = "2025, Authors"
+author = "Authors"
+release = "0.1"
+
+exclude_patterns = [ "_build" , "Thumbs.db" , ".DS_Store" ]
+
+extensions = [
+ "myst_parser" , # in order to use markdown
+ "autoapi.extension" , # in order to use markdown
+ ]
+
+# search this directory for Python files
+autoapi_dirs = [ ".." ]
+
+# ignore this file when generating API documentation
+autoapi_ignore = [ "*/conf.py" ]
+
+myst_enable_extensions = [
+ "colon_fence" , # ::: can be used instead of ``` for better rendering
+]
+
+html_theme = "sphinx_rtd_theme"
+
+
+Then rebuild the documentation (or push the changes and let GitHub rebuild it)
+and you should see a new section “API Reference”.
+
+
+Possibilities to host Sphinx documentation
+
+
+
+Confused about reStructuredText vs. Markdown vs. MyST?
+
+At the beginning there was reStructuredText and Sphinx was built for reStructuredText.
+Independently, Markdown was invented and evolved into a couple of flavors.
+Markdown became more and more popular but was limited compared to reStructuredText.
+Later, MyST
+was invented to be able to write
+something that looks like Markdown but in addition can do everything that
+reStructuredText can do with extra directives.
+
+
+
+
+
+Collaborative version control and code review
+
+
+Concepts around collaboration
+
+
+Commits, branches, repositories, forks, clones
+
+repository : The project, contains all data and history (commits, branches, tags).
+commit : Snapshot of the project, gets a unique identifier (e.g. c7f0e8bfc718be04525847fc7ac237f470add76e
).
+branch : Independent development line. The main development line is often called main
.
+tag : A pointer to one commit, to be able to refer to it later. Like a commemorative plaque
+that you attach to a particular commit (e.g. phd-printed
or paper-submitted
).
+cloning : Copying the whole repository to your laptop - the first time. It is not necessary to download each file one by one.
+forking : Taking a copy of a repository (which is typically not yours) - your
+copy (fork) stays on GitHub/GitLab and you can make changes to your copy.
+
+
+
+Cloning a repository
+In order to make a complete copy a whole repository, the git clone
command
+can be used. When cloning, all the files, of all or selected branches, of a
+repository are copied in one operation. Cloning of a repository is of relevance
+in a few different situations:
+
+Working on your own, cloning is the operation that you can use to create
+multiple instances of a repository on, for instance, a personal computer, a
+server, and a supercomputer.
+The parent repository could be a repository that you or your colleague own. A
+common use case for cloning is when working together within a smaller team
+where everyone has read and write access to the same git repository.
+Alternatively, cloning can be made from a public repository of a code that
+you would like to use. Perhaps you have no intention to work on the code, but
+would like to stay in tune with the latest developments, also in-between
+releases of new versions of the code.
+
+
+
+
+
+Forking and cloning
+
+
+
+
+Forking a repository
+When a fork is made on GitHub/GitLab a complete copy, of all or selected
+branches, of the repository is made. The copy will reside under a different
+account on GitHub/GitLab. Forking of a repository is of high relevance when
+working with a git repository to which you do not have write access.
+
+In the fork repository commits can be made to the base branch (main
or
+master
), and to other branches.
+The commits that are made within the branches of the fork repository can be
+contributed back to the parent repository by means of pull or merge requests.
+
+
+
+Synchronizing changes between repositories
+
+We need a mechanism to communicate changes between the repositories.
+We will pull or fetch updates from remote repositories (we will soon discuss the difference between pull and fetch).
+We will push updates to remote repositories.
+We will learn how to suggest changes within repositories on GitHub and across repositories (pull request ).
+Repositories that are forked or cloned do not automatically synchronize themselves:
+We will learn how to update forks (by pulling from the “central” repository).
+A main difference between cloning a repository and forking a repository is that the former is a general operation for generating copies of a repository to different computers, whereas forking is a particular operation implemented on GitHub/GitLab.
+
+
+
+
+Collaborating within the same repository
+In this episode, we will learn how to collaborate within the same repository.
+We will learn how to cross-reference issues and pull requests, how to review
+pull requests, and how to use draft pull requests.
+This exercise will form a good basis for collaboration that is suitable for
+most research groups.
+
+
Note
+
When you read or hear pull request , please think of a change proposal .
+
+
+Exercise
+In this exercise, we will contribute to a repository via a pull request .
+This means that you propose some change, and then it is accepted (or not).
+
+
Exercise preparation
+
+First we need to get access to the exercise repository to which we will
+contribute.
+
+
+Don’t forget to accept the invitation
+
+Check https://github.com/settings/organizations/
+Alternatively check the inbox for the email account you registered with
+GitHub. GitHub emails you an invitation link, but if you don’t receive it
+you can go to your GitHub notifications in the top right corner. The
+maintainer can also “copy invite link” and share it within the group.
+
+
+Watching and unwatching repositories
+
+Now that you are a collaborator, you get notified about new issues and pull
+requests via email.
+If you do not wish this, you can “unwatch” a repository (top of
+the project page).
+However, we recommend watching repositories you are interested
+in. You can learn things from experts just by watching the
+activity that come through a popular project.
+
+
+
+
+Unwatch a repository by clicking “Unwatch” in the repository view,
+then “Participating and @mentions” - this way, you will get
+notifications about your own interactions.
+
+
+
+
+
+
+
Exercise: Collaborating within the same repository (25 min)
+
Technical requirements (from installation instructions):
+
+
What is familiar from the previous workshop day (not repeated here):
+
+
What will be new in this exercise:
+
+If you create the changes locally, you will need to push them to the remote repository.
+Learning what a protected branch is and how to modify a protected branch: using a pull request.
+Cross-referencing issues and pull requests.
+Practice to review a pull request.
+Learn about the value of draft pull requests.
+
+
Exercise tasks :
+
+Start in the exercise
+repository and open an
+issue where you describe the change you want to make. Note down the issue
+number since you will need it later.
+Create a new branch.
+Make a change to the recipe book on the new branch and in the commit
+cross-reference the issue you opened (see the walk-through below
+for how to do that).
+Push your new branch (with the new commit) to the repository you
+are working on.
+Open a pull request towards the main branch.
+Review somebody else’s pull request and give constructive feedback. Merge their pull request.
+Try to create a new branch with some half-finished work and open a draft
+pull request. Verify that the draft pull request cannot be merged since it
+is not meant to be merged yet.
+
+
+
+
+Solution and hints
+
+(1) Opening an issue
+This is done through the GitHub web interface. For example, you could
+give the name of the recipe you want to add (so that others don’t add
+the same one). It is the “Issues” tab.
+
+
+(2) Create a new branch.
+If on GitHub, you can make the branch in the web interface.
+
+
+(3) Make a change adding the recipe
+Add a new file with the recipe in it. Commit the file. In the commit
+message, include the note about the issue number, saying that this
+will close that issue.
+
+Cross-referencing issues and pull requests
+Each issue and each pull request gets a number and you can cross-reference them.
+When you open an issue, note down the issue number (in this case it is #2
):
+
+
+
+
+You can reference this issue number in a commit message or in a pull request, like in this
+commit message:
+ this is the new recipe; fixes #2
+
+
+If you forget to do that in your commit message, you can also reference the issue
+in the pull request description. And instead of fixes
you can also use closes
or resolves
+or fix
or close
or resolve
(case insensitive).
+Here are all the keywords that GitHub recognizes:
+https://help.github.com/en/articles/closing-issues-using-keywords
+Then observe what happens in the issue once your commit gets merged: it will
+automatically close the issue and create a link between the issue and the
+commit. This is very useful for tracking what changes were made in response to
+which issue and to know from when until when precisely the issue was open.
+
+
+
+(4) Push to GitHub as a new branch
+Push the branch to the repository. You should end up with a branch
+visible in the GitHub web view.
+This is only necessary if you created the changes locally. If you created the
+changes directly on GitHub, you can skip this step.
+
+
VS Code Command line
In VS Code, you can “publish the branch” to the remote repository by clicking
+the cloud icon in the bottom left corner of the window:
+
+
+
+
+
If you have a local branch my-branch
and you want to push it to the remote
+and make it visible there, first verify what your remote is:
+
$ git remote --verbose
+
+origin git@github.com:USER/recipe-book.git (fetch)
+origin git@github.com:USER/recipe-book.git (push)
+
+
+
In this case the remote is called origin
and refers to the address
+git@github.com:USER/recipe-book.git. Both can be used
+interchangeably. Make sure it points to the right repository, ideally a
+repository that you can write to.
+
Now that you have a remote, you can push your branch to it:
+
$ git push origin my-branch
+
+
+
This will create a new branch on the remote repository with the same name as
+your local branch.
+
You can also do this:
+
$ git push --set-upstream origin my-branch
+
+
+
This will connect the local branch and the remote branch so that in the future
+you can just type git push
and git pull
without specifying the branch name
+(but this depends on your Git configuration).
+
Troubleshooting
+
If you don’t have a remote yet, you can add it with (adjust ADDRESS
to your repository address):
+
$ git remote add origin ADDRESS
+
+
+
ADDRESS is the part that you copy from here:
+
+
+
+
+
If the remote points to the wrong place, you can change it with:
+
$ git remote set-url origin NEWADDRESS
+
+
+
+
+
+(5) Open a pull request towards the main branch
+This is done through the GitHub web interface.
+
+
+(6) Reviewing pull requests
+You review through the GitHub web interface.
+Checklist for reviewing a pull request:
+
+Be kind, on the other side is a human who has put effort into this.
+Be constructive: if you see a problem, suggest a solution.
+Towards which branch is this directed?
+Is the title descriptive?
+Is the description informative?
+Scroll down to see commits.
+Scroll down to see the changes.
+If you get incredibly many changes, also consider the license or copyright
+and ask where all that code is coming from.
+Again, be kind and constructive.
+Later we will learn how to suggest changes directly in the pull request.
+
+If someone is new, it’s often nice to say something encouraging in the
+comments before merging (even if it’s just “thanks”). If all is good
+and there’s not much else to say, you could merge directly.
+
+
+(7) Draft pull requests
+Try to create a draft pull request:
+
+
+
+
+Verify that the draft pull request cannot be merged until it is marked as ready
+for review:
+
+
+
+
+Draft pull requests can be useful for:
+
+Feedback : You can open a pull request early to get feedback on your work without
+signaling that it is ready to merge.
+Information : They can help communicating to others that a change is coming up and in
+progress.
+
+
+
+What is a protected branch? And how to modify it?
+A protected branch on GitHub or GitLab is a branch that cannot (accidentally)
+deleted or force-pushed to. It is also possible to require that a branch cannot
+be directly pushed to or modified, but that changes must be submitted via a
+pull request.
+To protect a branch in your own repository, go to “Settings” -> “Branches”.
+
+
+Summary
+
+We used all the same pieces that we’ve learned previously.
+But we successfully contributed to a collaborative project !
+The pull request allowed us to contribute without changing directly:
+this is very good when it’s not mainly our project.
+
+
+
+
+
+Practicing code review
+In this episode we will practice the code review process. We will learn how to
+ask for changes in a pull request, how to suggest a change in a pull request,
+and how to modify a pull request.
+This will enable research groups to work more collaboratively and to not only
+improve the code quality but also to learn from each other .
+
+Exercise
+
+
Exercise preparation
+
We can continue in the same exercise repository which we have used in the
+previous episode.
+
+
+
Exercise: Practicing code review (25 min)
+
Technical requirements :
+
+
What is familiar from previous lessons:
+
+
What will be new in this exercise:
+
+As a reviewer, we will learn how to ask for changes in a pull request.
+As a reviewer, we will learn how to suggest a change in a pull request.
+As a submitter, we will learn how to modify a pull request without closing
+the incomplete one and opening a new one.
+
+
Exercise tasks :
+
+Create a new branch and one or few commits: in these improve something but also
+deliberately introduce a typo and also a larger mistake which we will want to fix during the code review.
+Open a pull request towards the main branch.
+As a reviewer to somebody else’s pull request, ask for an improvement and
+also directly suggest a change for the small typo. (Hint:
+suggestions are possible through the GitHub web interface, view of
+a pull request, “Files changed” view, after selecting some lines.
+Look for the “±” button.)
+As the submitter, learn how to accept the suggested change. (Hint:
+GitHub web interface, “Files Changed” view.)
+As the submitter, improve the pull request without having to close and open
+a new one: by adding a new commit to the same branch. (Hint: push
+to the branch again.)
+Once the changes are addressed, merge the pull request.
+
+
+
+
+Help and discussion
+From here on out, we don’t give detailed steps to the solution. You
+need to combine what you know, and the extra info below, in order to
+solve the above.
+
+How to ask for changes in a pull request
+Technically, there are at least two common ways to ask for changes in a pull
+request.
+Either in the comment field of the pull request:
+
+
+
+
+Or by using the “Review changes”:
+
+
+
+
+And always please be kind and constructive in your comments. Remember that the
+goal is not gate-keeping but collaborative learning .
+
+
+How to suggest a change in a pull request as a reviewer
+If you see a very small problem that is easy to fix, you can suggest a change
+as a reviewer.
+Instead of asking the submitter to tiny problem, you can suggest a change by
+clicking on the plus sign next to the line number in the “Files changed” tab:
+
+
+
+
+Here you can comment on specific lines or even line ranges.
+But now the interesting part is to click on the “Add a suggestion” symbol (the
+one that looks like plus and minus). Now you can fix the tiny problem (in this
+case a typo) and then click on the “Add single comment” button:
+
+
+
+
+The result is this and the submitter can accept the change with a single click:
+
+
+
+
+After accepting with “Commit suggestion”, the improvement gets added to the
+pull request.
+
+
+
+
+Summary
+
+Our process isn’t just about code now. It’s about discussion and
+working together to make the whole process better.
+GitHub (or GitLab) discussions and reviewing are quite powerful and can make
+small changes easy.
+
+
+
+
+How to contribute changes to repositories that belong to others
+In this episode we prepare you to suggest and contribute changes to
+repositories that belong to others. These might be open source projects that
+you use in your work.
+We will see how Git and services like GitHub or GitLab can be used to suggest
+modification without having to ask for write access to the repository and
+accept modifications without having to grant write access to others.
+
+Exercise
+
+
Exercise preparation
+
+The exercise repository is now different:
+https://github.com/workshop-material/recipe-book-forking-exercise (note the -forking-exercise ).
+First fork the exercise repository to your GitHub account.
+Then clone your fork to your computer (if you wish to work locally).
+Double-check that you have forked the correct repository.
+
+
+
+
Exercise: Collaborating within the same repository (25 min)
+
Technical requirements :
+
+
What is familiar from previous lessons:
+
+
What will be new in this exercise:
+
+Opening a pull request towards the upstream repository.
+Pull requests can be coupled with automated testing.
+Learning that your fork can get out of date.
+After the pull requests are merged, updating your fork with the changes.
+Learn how to approach other people’s repositories with ideas, changes, and requests.
+
+
Exercise tasks :
+
+Open an issue in the upstream exercise repository where you describe the
+change you want to make. Take note of the issue number.
+Create a new branch in your fork of the repository.
+Make a change to the recipe book on the new branch and in the commit cross-reference the issue you opened.
+See the walk-through below for how to do this.
+Open a pull request towards the upstream repository.
+The instructor will review and merge the pull requests.
+During the review, pay attention to the automated test step (here for
+demonstration purposes, we test whether the recipe contains an ingredients
+and an instructions sections).
+After few pull requests are merged, update your fork with the changes.
+Check that in your fork you can see changes from other people’s pull requests.
+
+
+
+
+Help and discussion
+
+Help! I don’t have permissions to push my local changes
+Maybe you see an error like this one:
+ Please make sure you have the correct access rights
+and the repository exists.
+
+
+Or like this one:
+ failed to push some refs to workshop-material/recipe-book-forking-exercise.git
+
+
+In this case you probably try to push the changes not to your fork but to the original repository
+and in this exercise you do not have write access to the original repository.
+The simpler solution is to clone again but this time your fork.
+
+
Recovery
+
But if you want to keep your local changes, you can change the remote URL to point to your fork.
+Check where your remote points to with git remote --verbose
.
+
It should look like this (replace USER
with your GitHub username):
+
$ git remote --verbose
+
+origin git@github.com:USER/recipe-book-forking-exercise.git (fetch)
+origin git@github.com:USER/recipe-book-forking-exercise.git (push)
+
+
+
It should not look like this:
+
$ git remote --verbose
+
+origin git@github.com:workshop-material/recipe-book-forking-exercise.git (fetch)
+origin git@github.com:workshop-material/recipe-book-forking-exercise.git (push)
+
+
+
In this case you can adjust “origin” to point to your fork with:
+
$ git remote set-url origin git@github.com:USER/recipe-book-forking-exercise.git
+
+
+
+
+
+Opening a pull request towards the upstream repository
+We have learned in the previous episode that pull requests are always from
+branch to branch. But the branch can be in a different repository.
+When you open a pull request in a fork, by default GitHub will suggest to
+direct it towards the default branch of the upstream repository.
+This can be changed and it should always be verified, but in this case this is
+exactly what we want to do, from fork towards upstream:
+
+
+
+
+
+
+Pull requests can be coupled with automated testing
+We added an automated test here just for fun and so that you see that this is
+possible to do.
+In this exercise, the test is silly. It will check whether the recipe contains
+both an ingredients and an instructions section.
+In this example the test failed:
+
+
+
+
+Click on the “Details” link to see the details of the failed test:
+
+
+
+
+How can this be useful?
+
+The project can define what kind of tests are expected to pass before a pull
+request can be merged.
+The reviewer can see the results of the tests, without having to run them
+locally.
+
+How does it work?
+
+What tests or steps can you image for your project to run automatically with
+each pull request?
+
+
+How to update your fork with changes from upstream
+This used to be difficult but now it is two mouse clicks.
+Navigate to your fork and notice how GitHub tells you that your fork is behind.
+In my case, it is 9 commits behind upstream. To fix this, click on “Sync fork”
+and then “Update branch”:
+
+
+
+
+After the update my “branch is up to date” with the upstream repository:
+
+
+
+
+
+
+How to approach other people’s repositories with ideas, changes, and requests
+Contributing very minor changes
+
+If you observe an issue and have an idea how to fix it
+
+Open an issue in the repository you wish to contribute to
+Describe the problem
+If you have a suggestion on how to fix it, describe your suggestion
+Possibly discuss and get feedback
+If you are working on the fix, indicate it in the issue so that others know that somebody is working on it and who is working on it
+Submit your fix as pull request or merge request which references/closes the issue
+
+
+If you have an idea for a new feature
+
+Open an issue in the repository you wish to contribute to
+In the issue, write a short proposal for your suggested change or new feature
+Motivate why and how you wish to do this
+Also indicate where you are unsure and where you would like feedback
+Discuss and get feedback before you code
+Once you start coding, indicate that you are working on it
+Once you are done, submit your new feature as pull request or merge request which references/closes the issue/proposal
+
+
+
Motivation
+
+Get agreement and feedback before writing 5000 lines of code which might be rejected
+If we later wonder why something was done, we have the issue/proposal as
+reference and can read up on the reasoning behind a code change
+
+
+
+
+
+Summary
+
+This forking workflow lets you propose changes to repositories for
+which you have no write access .
+This is the way that much modern open-source software works.
+You can now contribute to any project you can view.
+
+
+
+
+
+
+Reproducible environments and dependencies
+
+
Objectives
+
+There are not many codes that have no dependencies.
+How should we deal with dependencies ?
+We will focus on installing and managing dependencies in Python when using packages from PyPI and Conda.
+We will not discuss how to distribute your code as a package.
+
+
+[This episode borrows from https://coderefinery.github.io/reproducible-python/reusable/
+and https://aaltoscicomp.github.io/python-for-scicomp/dependencies/ ]
+Essential XKCD comics:
+
+
+How to avoid: “It works on my machine 🤷”
+Use a standard way to list dependencies in your project:
+
+Python: requirements.txt
or environment.yml
+R: DESCRIPTION
or renv.lock
+Rust: Cargo.lock
+Julia: Project.toml
+C/C++/Fortran: CMakeLists.txt
or Makefile
or spack.yaml
or the module
+system on clusters or containers
+Other languages: …
+
+
+
+Two ecosystems: PyPI (The Python Package Index) and Conda
+
+
PyPI
+
+Installation tool: pip
or uv
or similar
+Traditionally used for Python-only packages or
+for Python interfaces to external libraries. There are also packages
+that have bundled external libraries (such as numpy).
+Pros:
+
+Easy to use
+Package creation is easy
+
+
+Cons:
+
+
+
+
+
+
Conda
+
+Installation tool: conda
or mamba
or similar
+Aims to be a more general package distribution tool
+and it tries to provide not only the Python packages, but also libraries
+and tools needed by the Python packages.
+Pros:
+
+
+Cons:
+
+
+
+
+
+
+Conda ecosystem explained
+
+Anaconda is a distribution of conda packages
+made by Anaconda Inc. When using Anaconda remember to check that your
+situation abides with their licensing terms (see below).
+Anaconda has recently changed its licensing terms , which affects its
+use in a professional setting. This caused uproar among academia
+and Anaconda modified their position in
+this article .
+Main points of the article are:
+
+conda (installation tool) and community channels (e.g. conda-forge)
+are free to use.
+Anaconda repository and Anaconda’s channels in the community repository
+are free for universities and companies with fewer than 200 employees.
+Non-university research institutions and national laboratories need
+licenses.
+Miniconda is free, when it does not download Anaconda’s packages.
+Miniforge is not related to Anaconda, so it is free.
+
+For ease of use on sharing environment files, we recommend using
+Miniforge to create the environments and using conda-forge as the main
+channel that provides software.
+
+Major repositories/channels:
+
+Anaconda Repository
+houses Anaconda’s own proprietary software channels.
+Anaconda’s proprietary channels: main
, r
, msys2
and anaconda
.
+These are sometimes called defaults
.
+conda-forge is the largest open source
+community channel. It has over 28k packages that include open-source
+versions of packages in Anaconda’s channels.
+
+
+
+
+
+
+Best practice: Install dependencies into isolated environments
+
+For each project, create a separate environment .
+Don’t install dependencies globally for all projects. Sooner or later, different projects will have conflicting dependencies.
+Install them from a file which documents them at the same time
+Install dependencies by first recording them in requirements.txt
or
+environment.yml
and install using these files, then you have a trace
+(we will practice this later below).
+
+
+
Keypoints
+
If somebody asks you what dependencies you have in your project, you should be
+able to answer this question with a file .
+
In Python, the two most common ways to do this are:
+
+
You can export (“freeze”) the dependencies from your current environment into these files:
+
# inside a conda environment
+$ conda env export --from-history > environment.yml
+
+# inside a virtual environment
+$ pip freeze > requirements.txt
+
+
+
+
+
+How to communicate the dependencies as part of a report/thesis/publication
+Each notebook or script or project which depends on libraries should come with
+either a requirements.txt
or a environment.yml
, unless you are creating
+and distributing this project as Python package.
+
+Attach a requirements.txt
or a environment.yml
to your thesis.
+Even better: Put requirements.txt
or a environment.yml
in your Git repository along your code.
+Even better: Also binderize your analysis pipeline.
+
+
+
+Containers
+
+A container is like an operating system inside a file .
+“Building a container”: Container definition file (recipe) -> Container image
+This can be used with Apptainer /
+SingularityCE .
+
+Containers offer the following advantages:
+
+Reproducibility : The same software environment can be recreated on
+different computers. They force you to know and document all your dependencies .
+Portability : The same software environment can be run on different computers.
+Isolation : The software environment is isolated from the host system.
+“Time travel ”:
+
+
+
+
+
+How to install dependencies into environments
+Now we understand a bit better why and how we installed dependencies
+for this course in the Software install instructions .
+We have used Miniforge and the long command we have used was:
+$ mamba env create -n course -f https://raw.githubusercontent.com/coderefinery/reproducible-python-ml/main/software/environment.yml
+
+
+This command did two things:
+
+Create a new environment with name “course” (specified by -n
).
+Installed all dependencies listed in the environment.yml
file (specified by
+-f
), which we fetched directly from the web.
+Here
+you can browse it.
+
+For your own projects:
+
+Start by writing an environment.yml
of requirements.txt
file. They look like this:
+
+
+
environment.yml requirements.txt
name : course
+channels :
+ - conda-forge
+ - bioconda
+dependencies :
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - jupyterlab
+ - pytest
+ - scalene
+ - flit
+ - ruff
+ - icecream
+ - snakemake-minimal
+ - myst-parser
+ - sphinx
+ - sphinx-rtd-theme
+ - sphinx-autoapi
+ - sphinx-autobuild
+ - black
+ - isort
+ - pip
+ - pip :
+ - jupyterlab-code-formatter
+
+
+
click
+numpy
+pandas
+scipy
+altair
+vl - convert - python
+jupyterlab
+pytest
+scalene
+flit
+ruff
+icecream
+myst - parser
+sphinx
+sphinx - rtd - theme
+sphinx - autoapi
+sphinx - autobuild
+black
+isort
+jupyterlab - code - formatter
+
+
+
+
+Then set up an isolated environment and install the dependencies from the file into it:
+
+
+
Miniforge Pixi Virtual environment uv
+Create a new environment with name “myenv” from environment.yml
:
+$ conda env create -n myenv -f environment.yml
+
+
+Or equivalently:
+$ mamba env create -n myenv -f environment.yml
+
+
+
+Activate the environment:
+
+
+Run your code inside the activated virtual environment.
+
+
+
+
+Create a virtual environment by running (the second argument is the name
+of the environment and you can change it):
+
+
+Activate the virtual environment (how precisely depends on your operating
+system and shell).
+Install the dependencies:
+$ python -m pip install -r requirements.txt
+
+
+
+Run your code inside the activated virtual environment.
+
+
+
+
+Create a virtual environment by running (the second argument is the name
+of the environment and you can change it):
+
+
+Activate the virtual environment (how precisely depends on your operating
+system and shell).
+Install the dependencies:
+$ uv pip sync requirements.txt
+
+
+
+Run your code inside the virtual environment.
+$ uv run python example.py
+
+
+
+
+
+
+
+Updating environments
+What if you forgot a dependency? Or during the development of your project
+you realize that you need a new dependency? Or you don’t need some dependency anymore?
+
+Modify the environment.yml
or requirements.txt
file.
+Either remove your environment and create a new one, or update the existing one:
+
+
+
Miniforge Pixi Virtual environment uv
+
+
+Pinning package versions
+Let us look at the
+environment.yml
+which we used to set up the environment for this course.
+Dependencies are listed without version numbers. Should we pin the
+versions ?
+
+Both pip
and conda
ecosystems and all the tools that we have
+mentioned support pinning versions.
+It is possible to define a range of versions instead of precise versions.
+While your project is still in progress, I often use latest versions and do not pin them.
+When publishing the script or notebook, it is a good idea to pin the versions
+to ensure that the code can be run in the future.
+Remember that at some point in time you will face a situation where
+newer versions of the dependencies are no longer compatible with your
+software. At this point you’ll have to update your software to use the newer
+versions or to lock it into a place in time.
+
+
+
+Managing dependencies on a supercomputer
+
+Additional challenges:
+
+Storage quotas: Do not install dependencies in your home directory . A conda environment can easily contain 100k files.
+Network file systems struggle with many small files. Conda environments often contain many small files.
+
+
+Possible solutions:
+
+Try Pixi (modern take on managing Conda environments) and
+uv (modern take on managing virtual
+environments). Blog post: Using Pixi and uv on a supercomputer
+Install your environment on the fly into a scratch directory on local disk (not the network file system).
+Install your environment on the fly into a RAM disk/drive.
+Containerize your environment into a container image.
+
+
+
+
+
+
Keypoints
+
+Being able to communicate your dependencies is not only nice for others, but
+also for your future self or the next PhD student or post-doc.
+If you ask somebody to help you with your code, they will ask you for the
+dependencies.
+
+
+
+
+
+Automated testing
+
+
Objectives
+
+Know where to start in your own project.
+Know what possibilities and techniques are available in the Python world.
+Have an example for how to make the testing part of code review .
+
+
+
+
Instructor note
+
+(15 min) Motivation
+(15 min) End-to-end tests
+(15 min) Pytest
+(15 min) Adding the unit test to GitHub Actions
+(10 min) What else is possible
+(20 min) Exercise
+
+
+
+Motivation
+Testing is a way to check that the code does what it is expected to.
+
+Less scary to change code : tests will tell you whether something broke.
+Easier for new people to join.
+Easier for somebody to revive an old code .
+End-to-end test : run the whole code and compare result to a reference.
+Unit tests : test one unit (function or module). Can guide towards better
+structured code: complicated code is more difficult to test.
+
+
+
+How testing is often taught
+def add ( a , b ):
+ return a + b
+
+
+def test_add ():
+ assert add ( 1 , 2 ) == 3
+
+
+How this feels:
+
+
+
+
+[Citation needed]
+
+
+Instead, we will look at and discuss a real example where we test components
+from our example project.
+
+
+Where to start
+Do I even need testing? :
+
+If you have nothing yet :
+
+Start with an end-to-end test.
+Describe in words how you check whether the code still works.
+Translate the words into a script (any language).
+Run the script automatically on every code change (GitHub Actions or GitLab CI).
+
+If you want to start with unit-testing :
+
+You want to rewrite a function? Start adding a unit test right there first.
+You spend few days chasing a bug? Once you fix it, add a test to make sure it does not come back.
+
+
+
+
+Pytest
+Here is a simple example of a test:
+def fahrenheit_to_celsius ( temp_f ):
+ """Converts temperature in Fahrenheit
+ to Celsius.
+ """
+ temp_c = ( temp_f - 32.0 ) * ( 5.0 / 9.0 )
+ return temp_c
+
+
+# this is the test function
+def test_fahrenheit_to_celsius ():
+ temp_c = fahrenheit_to_celsius ( temp_f = 100.0 )
+ expected_result = 37.777777
+ # assert raises an error if the condition is not met
+ assert abs ( temp_c - expected_result ) < 1.0e-6
+
+
+To run the test(s):
+
+Explanation: pytest
will look for functions starting with test_
in files
+and directories given as arguments. It will run them and report the results.
+Good practice to add unit tests:
+
+Add the test function and run it.
+Break the function on purpose and run the test.
+Does the test fail as expected?
+
+
+
+Adding the unit test to GitHub Actions
+Our next goal is that we want GitHub to run the unit test
+automatically on every change.
+First we need to extend our
+environment.yml :
+name : classification-task
+channels :
+ - conda-forge
+dependencies :
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - pytest
+
+
+We also need to extend .github/workflows/test.yml
(highlighted line):
+name : Test
+
+on :
+ push :
+ branches : [ main ]
+ pull_request :
+ branches : [ main ]
+
+jobs :
+ build :
+ runs-on : ubuntu-24.04
+
+ steps :
+ - name : Checkout
+ uses : actions/checkout@v4
+
+ - uses : mamba-org/setup-micromamba@v1
+ with :
+ micromamba-version : '2.0.5-0' # any version from https://github.com/mamba-org/micromamba-releases
+ environment-file : environment.yml
+ init-shell : bash
+ cache-environment : true
+ post-cleanup : 'all'
+ generate-run-shell : false
+
+ - name : Run tests
+ run : |
+ ./test.sh
+ pytest generate-predictions.py
+ shell : bash -el {0}
+
+
+In the above example, we assume that we added a test function to generate-predictions.py
.
+If we have time, we can try to create a pull request which would break the
+code and see how the test fails.
+
+
+What else is possible
+
+Run the test set automatically on every code change:
+
+
+The testing above used example-based testing.
+Test coverage : how much of the code is traversed by tests?
+
+
+Property-based testing: generates arbitrary data matching your specification and checks that your guarantee still holds in that case.
+
+
+Snapshot-based testing: makes it easier to generate snapshots for regression tests.
+
+
+Mutation testing : tests pass -> change a line of code (make a mutant) -> test again and check whether all mutants get “killed”.
+
+
+
+
+
+Exercises
+
+
Exercise
+
Experiment with the example project and what we learned above or try it on
+the example project or on your own project :
+
+Add a unit test. If you are unsure where to start , you can try to move
+the majority
+vote
+into a separate function and write a test function for it.
+Try to run pytest locally.
+Check whether it fails when you break the corresponding function.
+Try to run it on GitHub Actions.
+Create a pull request which would break the code and see whether the automatic test would catch it.
+Try to design an end-to-end test for your project. Already the thought
+process can be very helpful.
+
+
+
+
+
+
+Sharing notebooks
+
+[this lesson is adapted after https://coderefinery.github.io/jupyter/sharing/ ]
+
+Document dependencies
+
+If you import libraries into your notebook, note down their versions.
+Document the dependencies as discussed in section Reproducible environments and dependencies .
+Place either environment.yml
or requirements.txt
in the same folder as
+the notebook(s).
+If you publish the notebook as part of a publication, it is probably a good
+idea to pin the versions of the libraries you use.
+This is not only useful for people who will try to rerun this in future, it
+is also understood by some tools (e.g. Binder ) which we
+will see later.
+
+
+
+Different ways to share a notebook
+We need to learn how to share notebooks. At the minimum we need
+to share them with our future selves (backup and reproducibility).
+
+The following platforms can be used free of charge but have paid subscriptions for
+faster access to cloud resources:
+
+
+
+Sharing dynamic notebooks using Binder
+
+
Exercise/demo: Making your notebooks reproducible by anyone (15 min)
+
Instructor demonstrates this:
+
+First we look at the statically rendered version of the example
+notebook
+on GitHub and also nbviewer .
+Visit https://mybinder.org :
+
+
+
+
+Check that your notebook repository now has a “launch binder”
+badge in your README.md
file on GitHub.
+Try clicking the button and see how your repository is launched
+on Binder (can take a minute or two). Your notebooks can now be
+explored and executed in the cloud.
+Enjoy being fully reproducible!
+
+
+
+
+How to get a digital object identifier (DOI)
+
+Zenodo is a great service to get a
+DOI for a notebook
+(but first practice with the Zenodo sandbox ).
+Binder can also run notebooks from Zenodo.
+In the supporting information of your paper you can refer to its DOI.
+
+
+
+
+Concepts in refactoring and modular code design
+
+Starting questions for the collaborative document
+
+What does “modular code development” mean for you?
+What best practices can you recommend to arrive at well structured,
+modular code in your favourite programming language?
+What do you know now about programming that you wish somebody told you
+earlier?
+Do you design a new code project on paper before coding? Discuss pros and
+cons.
+Do you build your code top-down (starting from the big picture) or
+bottom-up (starting from components)? Discuss pros and cons.
+Would you prefer your code to be 2x slower if it was easier to read and
+understand?
+
+
+
+Pure functions
+
+Pure functions have no notion of state: They take input values and return
+values
+Given the same input, a pure function always returns the same value
+Function calls can be optimized away
+Pure function == data
+
+a) pure: no side effects
+def fahrenheit_to_celsius ( temp_f ):
+ temp_c = ( temp_f - 32.0 ) * ( 5.0 / 9.0 )
+ return temp_c
+
+temp_c = fahrenheit_to_celsius ( temp_f = 100.0 )
+print ( temp_c )
+
+
+b) stateful: side effects
+f_to_c_offset = 32.0
+f_to_c_factor = 0.555555555
+temp_c = 0.0
+
+def fahrenheit_to_celsius_bad ( temp_f ):
+ global temp_c
+ temp_c = ( temp_f - f_to_c_offset ) * f_to_c_factor
+
+fahrenheit_to_celsius_bad ( temp_f = 100.0 )
+print ( temp_c )
+
+
+Pure functions are easier to:
+
+Test
+Understand
+Reuse
+Parallelize
+Simplify
+Optimize
+Compose
+
+Mathematical functions are pure:
+
+\[f(x, y) = x - x^2 + x^3 + y^2 + xy\]
+
+\[(f \circ g)(x) = f(g(x))\]
+Unix shell commands are stateless:
+ $ cat somefile | grep somestring | sort | uniq | ...
+
+
+
+
+But I/O and network and disk and databases are not pure!
+
+
+
+
+
+
+From classes to functions
+Object-oriented programming and functional programming both have their place
+and value .
+Here is an example of expressing the same thing in Python in 4 different ways.
+Which one do you prefer?
+
+As a class :
+import math
+
+
+class Moon :
+ def __init__ ( self , name , radius , contains_water = False ):
+ self . name = name
+ self . radius = radius # in kilometers
+ self . contains_water = contains_water
+
+ def surface_area ( self ) -> float :
+ """Calculate the surface area of the moon assuming a spherical shape."""
+ return 4.0 * math . pi * self . radius ** 2
+
+ def __repr__ ( self ):
+ return f "Moon(name= { self . name !r} , radius= { self . radius } , contains_water= { self . contains_water } )"
+
+
+europa = Moon ( name = "Europa" , radius = 1560.8 , contains_water = True )
+
+print ( europa )
+print ( f "Surface area (km^2) of { europa . name } : { europa . surface_area () } " )
+
+
+
+As a dataclass :
+from dataclasses import dataclass
+import math
+
+
+@dataclass
+class Moon :
+ name : str
+ radius : float # in kilometers
+ contains_water : bool = False
+
+ def surface_area ( self ) -> float :
+ """Calculate the surface area of the moon assuming a spherical shape."""
+ return 4.0 * math . pi * self . radius ** 2
+
+
+europa = Moon ( name = "Europa" , radius = 1560.8 , contains_water = True )
+
+print ( europa )
+print ( f "Surface area (km^2) of { europa . name } : { europa . surface_area () } " )
+
+
+
+As a named tuple :
+import math
+from collections import namedtuple
+
+
+def surface_area ( radius : float ) -> float :
+ return 4.0 * math . pi * radius ** 2
+
+
+Moon = namedtuple ( "Moon" , [ "name" , "radius" , "contains_water" ])
+
+
+europa = Moon ( name = "Europa" , radius = 1560.8 , contains_water = True )
+
+print ( europa )
+print ( f "Surface area (km^2) of { europa . name } : { surface_area ( europa . radius ) } " )
+
+
+
+As a dict :
+import math
+
+
+def surface_area ( radius : float ) -> float :
+ return 4.0 * math . pi * radius ** 2
+
+
+europa = { "name" : "Europa" , "radius" : 1560.8 , "contains_water" : True }
+
+
+print ( europa )
+print ( f "Surface area (km^2) of { europa [ 'name' ] } : { surface_area ( europa [ 'radius' ]) } " )
+
+
+
+
+
+
+How to design your code before writing it
+
+
+
+
+
+Profiling
+
+
Objectives
+
+Understand when improving code performance is worth the time and effort.
+Knowing how to find performance bottlenecks in Python code.
+Try Scalene as one of many tools
+to profile Python code.
+
+
+[This page is adapted after https://aaltoscicomp.github.io/python-for-scicomp/profiling/ ]
+
+Should we even optimize the code?
+Classic quote to keep in mind: “Premature optimization is the root of all evil.” [Donald Knuth]
+
+
Discussion
+
It is important to ask ourselves whether it is worth it.
+
+
Depends. What does it depend on?
+
+
+
+Measure instead of guessing
+Before doing code surgery to optimize the run time or lower the memory usage,
+we should measure where the bottlenecks are. This is called profiling .
+Analogy: Medical doctors don’t start surgery based on guessing. They first measure
+(X-ray, MRI, …) to know precisely where the problem is.
+Not only programming beginners can otherwise guess wrong, but also experienced
+programmers can be surprised by the results of profiling.
+
+
+
+
+Tracing profilers vs. sampling profilers
+Tracing profilers record every function call and event in the program,
+logging the exact sequence and duration of events.
+
+Sampling profilers periodically samples the program’s state (where it is
+and how much memory is used), providing a statistical view of where time is
+spent.
+
+Pros:
+
+
+Cons:
+
+Less precise, potentially missing infrequent or short calls.
+Provides an approximation rather than exact timing.
+
+
+
+
+
Analogy: Imagine we want to optimize the London Underground (subway) system
+
We wish to detect bottlenecks in the system to improve the service and for this we have
+asked few passengers to help us by tracking their journey.
+
+Tracing : We follow every train and passenger, recording every stop
+and delay. When passengers enter and exit the train, we record the exact time
+and location.
+Sampling : Every 5 minutes the phone notifies the passenger to note
+down their current location. We then use this information to estimate
+the most crowded stations and trains.
+
+
+
+
+Choosing the right system size
+Sometimes we can configure the system size (for instance the time step in a simulation
+or the number of time steps or the matrix dimensions) to make the program finish sooner.
+For profiling, we should choose a system size that is representative of the real-world
+use case. If we profile a program with a small input size, we might not see the same
+bottlenecks as when running the program with a larger input size.
+Often, when we scale up the system size, or scale the number of processors, new bottlenecks
+might appear which we didn’t see before. This brings us back to: “measure instead of guessing”.
+
+
+Exercises
+
+
Exercise: Practicing profiling
+
In this exercise we will use the Scalene profiler to find out where most of the time is spent
+and most of the memory is used in a given code example.
+
Please try to go through the exercise in the following steps:
+
+Make sure scalene
is installed in your environment (if you have followed
+this course from the start and installed the recommended software
+environment, then it is).
+Download Leo Tolstoy’s “War and Peace” from the following link (the text is
+provided by Project Gutenberg ):
+https://www.gutenberg.org/cache/epub/2600/pg2600.txt
+(right-click and “save as” to download the file and save it as “book.txt” ).
+Before you run the profiler, try to predict in which function the code
+(the example code is below)
+will spend most of the time and in which function it will use most of the
+memory.
+Save the example code as example.py
and
+run the scalene
profiler on the following code example and browse the
+generated HTML report to find out where most of the time is spent and where
+most of the memory is used:
+
+Alternatively you can do this (and then open the generated file in a browser):
+$ scalene example.py --html > profile.html
+
+
+You can find an example of the generated HTML report in the solution below.
+
+Does the result match your prediction? Can you explain the results?
+
+
Example code (example.py
):
+
"""
+The code below reads a text file and counts the number of unique words in it
+(case-insensitive).
+"""
+import re
+
+
+def count_unique_words1 ( file_path : str ) -> int :
+ with open ( file_path , "r" , encoding = "utf-8" ) as file :
+ text = file . read ()
+ words = re . findall ( r "\b\w+\b" , text . lower ())
+ return len ( set ( words ))
+
+
+def count_unique_words2 ( file_path : str ) -> int :
+ unique_words = []
+ with open ( file_path , "r" , encoding = "utf-8" ) as file :
+ for line in file :
+ words = re . findall ( r "\b\w+\b" , line . lower ())
+ for word in words :
+ if word not in unique_words :
+ unique_words . append ( word )
+ return len ( unique_words )
+
+
+def count_unique_words3 ( file_path : str ) -> int :
+ unique_words = set ()
+ with open ( file_path , "r" , encoding = "utf-8" ) as file :
+ for line in file :
+ words = re . findall ( r "\b\w+\b" , line . lower ())
+ for word in words :
+ unique_words . add ( word )
+ return len ( unique_words )
+
+
+def main ():
+ # book.txt is downloaded from https://www.gutenberg.org/cache/epub/2600/pg2600.txt
+ _result = count_unique_words1 ( "book.txt" )
+ _result = count_unique_words2 ( "book.txt" )
+ _result = count_unique_words3 ( "book.txt" )
+
+
+if __name__ == "__main__" :
+ main ()
+
+
+
+
Solution
+
+
+
+
+Result of the profiling run for the above code example. You can click on the image to make it larger.
+
+
+
Results:
+
+
Explanation:
+
+The count_unique_words2
function is the slowest because it uses a list
+to store unique words and checks if a word is already in the list before
+adding it.
+Checking whether a list contains an element might require traversing the
+whole list, which is an O(n) operation. As the list grows in size,
+the lookup time increases with the size of the list.
+The count_unique_words1
and count_unique_words3
functions are faster
+because they use a set to store unique words.
+Checking whether a set contains an element is an O(1) operation.
+The count_unique_words1
function uses the most memory because it creates
+a list of all words in the text file and then creates a set from that
+list.
+The count_unique_words3
function uses less memory because it traverses
+the text file line by line instead of reading the whole file into memory.
+
+
What we can learn from this exercise:
+
+When processing large files, it can be good to read them line by line
+or in batches
+instead of reading the whole file into memory.
+It is good to get an overview over standard data structures and their
+advantages and disadvantages (e.g. adding an element to a list is fast but checking whether
+it already contains the element can be slow).
+
+
+
+
+
+Additional resources
+
+
+
+
+Choosing a software license
+
+
Objectives
+
+Knowing about what derivative work is and whether we can share it.
+Get familiar with terminology around licensing.
+We will add a license to our example project.
+
+
+
+Copyright and derivative work: Sampling/remixing
+
+
+
+
+[Midjourney, CC-BY-NC 4.0]
+
+
+
+
+[Midjourney, CC-BY-NC 4.0]
+
+Copyright controls whether and how we can distribute
+the original work or the derivative work .
+In the context of software it is more about
+being able to change and distribute changes .
+Changing and distributing software is similar to changing and distributing
+music
+You can do almost anything if you don’t distribute it
+
+Often we don’t have the choice :
+
+Can we distribute our changes with the research community or our future selves?
+
+
+Why software licenses matter
+You find some great code that you want to reuse for your own publication.
+
+This is good for the original author - you will cite them. Maybe other people who cite you will cite them.
+You modify and remix the code.
+Two years later … ⌛
+Time to publish: You realize there is no license to the original work 😱
+
+Now we have a problem :
+
+😬 “Best” case: You manage to publish the paper without the software/data.
+Others cannot build on your software and data.
+😱 Worst case: You cannot publish it at all.
+Journal requires that papers should come with data and software so that they are reproducible.
+
+
+
+Taxonomy of software licenses
+
+
+
+European Commission, Directorate-General for Informatics, Schmitz, P., European Union Public Licence (EUPL): guidelines July 2021, Publications Office, 2021, https://data.europa.eu/doi/10.2799/77160
+
+
+Comments:
+
+Arrows represent compatibility (A -> B: B can reuse A)
+Proprietary/custom: Derivative work typically not possible (no arrow goes from proprietary to open)
+Permissive: Derivative work does not have to be shared
+Copyleft/reciprocal: Derivative work must be made available under the same license terms
+NC (non-commercial) and ND (non-derivative) exist for data licenses but not really for software licenses
+
+Great resource for comparing software licenses : Joinup Licensing Assistant
+
+
+
+Exercise/demo
+
+
+
Discussion
+
+What if my code uses libraries like numpy
, pandas
, scipy
,
+altair
, etc. Do we need to look at their licenses? In other words,
+is our project derivative work of something else?
+
+
+
+
+
+More exercises
+
+
Exercise: What constitutes derivative work?
+
Which of these are derivative works? Also reflect/discuss how this affects the
+choice of license.
+
+A. Download some code from a website and add on to it
+B. Download some code and use one of the functions in your code
+C. Changing code you got from somewhere
+D. Extending code you got from somewhere
+E. Completely rewriting code you got from somewhere
+F. Rewriting code to a different programming language
+G. Linking to libraries (static or dynamic), plug-ins, and drivers
+H. Clean room design (somebody explains you the code but you have never seen it)
+I. You read a paper, understand algorithm, write own code
+
+
+
+
+
Exercise: Licensing situations
+
Consider some common licensing situations. If you are part of an exercise
+group, discuss these with others:
+
+What is the StackOverflow license for code you copy and paste?
+A journal requests that you release your software during publication. You have
+copied a portion of the code from another package, which you have forgotten.
+Can you satisfy the journal’s request?
+You want to fix a bug in a project someone else has released, but there is no license. What risks are there?
+How would you ask someone to add a license?
+You incorporate MIT, GPL, and BSD3 licensed code into your project. What possible licenses can you pick for your project?
+You do the same as above but add in another license that looks strong copyleft. What possible licenses can you use now?
+Do licenses apply if you don’t distribute your code? Why or why not?
+Which licenses are most/least attractive for companies with proprietary software?
+
+
+
Solution
+
+As indicated here , all publicly accessible user contributions are licensed under Creative Commons Attribution-ShareAlike license. See Stackoverflow Terms of service for more detailed information.
+“Standard” licensing rules apply. So in this case, you would need to remove the portion of code you have copied from another package before being able to release your software.
+By default you are no authorized to use the content of a repository when there is no license. And derivative work is also not possible by default. Other risks: it may not be clear whether you can use and distribute (publish) the bugfixed code. For the repo owners it may not be clear whether they can use and distributed the bugfixed code. However, the authors may have forgotten to add a license so we suggest you to contact the authors (e.g. make an issue) and ask whether they are willing to add a license.
+As mentionned in 3., the easiest is to fill an issue and explain the reasons why you would like to use this software (or update it).
+Combining software with different licenses can be tricky and it is important to understand compatibilities (or lack of compatibilities) of the various licenses. GPL license is the most protective (BSD and MIT are quite permissive) so for the resulting combined software you could use a GPL license. However, re-licensing may not be necessary.
+Derivative work would need to be shared under this strong copyleft license (e.g. AGPL or GPL), unless the components are only plugins or libraries.
+If you keep your code for yourself, you may think you do not need a license. However, remember that in most companies/universities, your employer is “owning” your work and when you leave you may not be allowed to “distribute your code to your future self”. So the best is always to add a license!
+The least attractive licenses for companies with proprietary software are licenses where you would need to keep an open license when creating derivative work. For instance GPL and and AGPL. The most attractive licenses are permissive licenses where they can reuse, modify and relicense with no conditions. For instance MIT, BSD and Apache License.
+
+
+
+
+
+
+How to publish your code
+
+
+Is putting software on GitHub/GitLab/… publishing?
+
+
+
+
+FAIR principles. (c) Scriberia for The Turing Way , CC-BY.
+
+
+Is it enough to make the code public for the code to remain findable and accessible ?
+
+No. Because nothing prevents me from deleting my GitHub repository or
+rewriting the Git history and we have no guarantee that GitHub will still be around in 10 years.
+Make your code citable and persistent :
+Get a persistent identifier (PID) such as DOI in addition to sharing the
+code publicly, by using services like Zenodo or
+similar services.
+
+
+
+How to make your software citable
+
+
Discussion: Explain how you currently cite software
+
+Do you cite software that you use? How?
+If I wanted to cite your code/scripts, what would I need to do?
+
+
+Checklist for making a release of your software citable :
+
+Assigned an appropriate license
+Described the software using an appropriate metadata format
+Clear version number
+Authors credited
+Procured a persistent identifier
+Added a recommended citation to the software documentation
+
+This checklist is adapted from: N. P. Chue Hong, A. Allen, A. Gonzalez-Beltran,
+et al., Software Citation Checklist for Developers (Version 0.9.0). Zenodo.
+2019b. (DOI )
+Our practical recommendations :
+
+This is an example of a simple CITATION.cff
file:
+cff-version : 1.2.0
+message : "If you use this software, please cite it as below."
+authors :
+ - family-names : Doe
+ given-names : Jane
+ orcid : https://orcid.org/1234-5678-9101-1121
+title : "My Research Software"
+version : 2.0.4
+doi : 10.5281/zenodo.1234
+date-released : 2021-08-11
+
+
+More about CITATION.cff
files:
+
+
+
+
+How to cite software
+
+
Great resources
+
+A. M. Smith, D. S. Katz, K. E. Niemeyer, and FORCE11 Software Citation
+Working Group, “Software citation principles,” PeerJ Comput. Sci., vol. 2,
+no. e86, 2016 (DOI )
+D. S. Katz, N. P. Chue Hong, T. Clark, et al., Recognizing the value of
+software: a software citation guide [version 2; peer review: 2 approved].
+F1000Research 2021, 9:1257 (DOI )
+N. P. Chue Hong, A. Allen, A. Gonzalez-Beltran, et al., Software Citation
+Checklist for Authors (Version 0.9.0). Zenodo. 2019a. (DOI )
+N. P. Chue Hong, A. Allen, A. Gonzalez-Beltran, et al., Software Citation
+Checklist for Developers (Version 0.9.0). Zenodo. 2019b. (DOI )
+
+
+Recommended format for software citation is to ensure the following information
+is provided as part of the reference (from Katz, Chue Hong, Clark,
+2021 which also contains
+software citation examples):
+
+Creator
+Title
+Publication venue
+Date
+Identifier
+Version
+Type
+
+
+
+Exercise/demo
+
+
Exercise
+
+We will add a CITATION.cff
file to our example repository.
+We will get a DOI using the Zenodo sandbox :
+
+
+We can try to create an example repository with a Jupyter Notebook and run it through Binder
+to make it persistent and citable.
+
+
+
+
+
+
+
+Creating a Python package and deploying it to PyPI
+
+
Objectives
+
In this episode we will create a pip-installable Python package and learn how
+to deploy it to PyPI. As example, we can use one of the Python scripts from our
+example repository.
+
+
+Creating a Python package with the help of Flit
+There are unfortunately many ways to package a Python project:
+
+setuptools
is the most common way to package a Python project. It is very
+powerful and flexible, but also can get complex.
+flit
is a simpler alternative to setuptools
. It is less versatile, but
+also easier to use.
+poetry
is a modern packaging tool which is more versatile than flit
and
+also easier to use than setuptools
.
+twine
is another tool to upload packages to PyPI.
+…
+
+
+
This will be a demo
+
+We will try to package the code together on the big screen.
+We will share the result on GitHub so that you can retrace the steps.
+In this demo, we will use Flit to package the code. From Why use Flit? :
+
+Make the easy things easy and the hard things possible is an old motto from
+the Perl community. Flit is entirely focused on the easy things part of that,
+and leaves the hard things up to other tools.
+
+
+
+
+
+
+
+Step 2: Testing an install from GitHub
+If a local install worked, push the pyproject.toml
to GitHub and try to install the package from GitHub.
+In a requirements.txt
file, you can specify the GitHub repository and the
+branch (adapt the names):
+git + https : // github . com / ORGANIZATION / REPOSITORY . git @main
+
+
+A corresponding envionment.yml
file for conda would look like this (adapt the
+names):
+name : experiment
+channels :
+ - conda-forge
+dependencies :
+ - python <= 3.12
+ - pip
+ - pip :
+ - git+https://github.com/ORGANIZATION/REPOSITORY.git@main
+
+
+Does it install and run? If yes, move on to the next step (test-PyPI and later
+PyPI).
+
+
+Step 3: Deploy the package to test-PyPI using GitHub Actions
+Our final step is to create a GitHub Actions workflow which will run when we
+create a new release.
+
+
Danger
+
I recommend to first practice this on the test-PyPI before deploying to the
+real PyPI.
+
+Here is the workflow (.github/workflows/package.yml
):
+name : Package
+
+on :
+ release :
+ types : [ created ]
+
+jobs :
+ build :
+ permissions : write-all
+ runs-on : ubuntu-24.04
+
+ steps :
+ - name : Switch branch
+ uses : actions/checkout@v4
+ - name : Set up Python
+ uses : actions/setup-python@v5
+ with :
+ python-version : "3.12"
+ - name : Install Flit
+ run : |
+ pip install flit
+ - name : Flit publish
+ run :
+ flit publish
+ env :
+ FLIT_USERNAME : __token__
+ FLIT_PASSWORD : ${{ secrets.PYPI_TOKEN }}
+ # uncomment the following line if you are using test.pypi.org:
+# FLIT_INDEX_URL: https://test.pypi.org/legacy/
+ # this is how you can test the installation from test.pypi.org:
+# pip install --index-url https://test.pypi.org/simple/ package_name
+
+
+About the FLIT_PASSWORD: ${{ secrets.PYPI_TOKEN }}
:
+
+
+
+
+
+
+Credit
+This lesson is a mashup of the following sources (all CC-BY):
+
+The lesson uses the following example repository:
+
+The classification task has replaced the “planets” example repository used in
+the original lesson.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/singlehtml/objects.inv b/branch/main/singlehtml/objects.inv
new file mode 100644
index 0000000..7e74d8f
Binary files /dev/null and b/branch/main/singlehtml/objects.inv differ
diff --git a/branch/main/software-licensing/index.html b/branch/main/software-licensing/index.html
new file mode 100644
index 0000000..863a3c5
--- /dev/null
+++ b/branch/main/software-licensing/index.html
@@ -0,0 +1,322 @@
+
+
+
+
+
+
+
+
+ Choosing a software license — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Choosing a software license
+
+
Objectives
+
+Knowing about what derivative work is and whether we can share it.
+Get familiar with terminology around licensing.
+We will add a license to our example project.
+
+
+
+Copyright and derivative work: Sampling/remixing
+
+
+
+
+[Midjourney, CC-BY-NC 4.0]
+
+
+
+
+[Midjourney, CC-BY-NC 4.0]
+
+Copyright controls whether and how we can distribute
+the original work or the derivative work .
+In the context of software it is more about
+being able to change and distribute changes .
+Changing and distributing software is similar to changing and distributing
+music
+You can do almost anything if you don’t distribute it
+
+Often we don’t have the choice :
+
+Can we distribute our changes with the research community or our future selves?
+
+
+Why software licenses matter
+You find some great code that you want to reuse for your own publication.
+
+This is good for the original author - you will cite them. Maybe other people who cite you will cite them.
+You modify and remix the code.
+Two years later … ⌛
+Time to publish: You realize there is no license to the original work 😱
+
+Now we have a problem :
+
+😬 “Best” case: You manage to publish the paper without the software/data.
+Others cannot build on your software and data.
+😱 Worst case: You cannot publish it at all.
+Journal requires that papers should come with data and software so that they are reproducible.
+
+
+
+Taxonomy of software licenses
+
+
+
+European Commission, Directorate-General for Informatics, Schmitz, P., European Union Public Licence (EUPL): guidelines July 2021, Publications Office, 2021, https://data.europa.eu/doi/10.2799/77160
+
+
+Comments:
+
+Arrows represent compatibility (A -> B: B can reuse A)
+Proprietary/custom: Derivative work typically not possible (no arrow goes from proprietary to open)
+Permissive: Derivative work does not have to be shared
+Copyleft/reciprocal: Derivative work must be made available under the same license terms
+NC (non-commercial) and ND (non-derivative) exist for data licenses but not really for software licenses
+
+Great resource for comparing software licenses : Joinup Licensing Assistant
+
+
+
+Exercise/demo
+
+
+
Discussion
+
+What if my code uses libraries like numpy
, pandas
, scipy
,
+altair
, etc. Do we need to look at their licenses? In other words,
+is our project derivative work of something else?
+
+
+
+
+
+More exercises
+
+
Exercise: What constitutes derivative work?
+
Which of these are derivative works? Also reflect/discuss how this affects the
+choice of license.
+
+A. Download some code from a website and add on to it
+B. Download some code and use one of the functions in your code
+C. Changing code you got from somewhere
+D. Extending code you got from somewhere
+E. Completely rewriting code you got from somewhere
+F. Rewriting code to a different programming language
+G. Linking to libraries (static or dynamic), plug-ins, and drivers
+H. Clean room design (somebody explains you the code but you have never seen it)
+I. You read a paper, understand algorithm, write own code
+
+
+
+
+
Exercise: Licensing situations
+
Consider some common licensing situations. If you are part of an exercise
+group, discuss these with others:
+
+What is the StackOverflow license for code you copy and paste?
+A journal requests that you release your software during publication. You have
+copied a portion of the code from another package, which you have forgotten.
+Can you satisfy the journal’s request?
+You want to fix a bug in a project someone else has released, but there is no license. What risks are there?
+How would you ask someone to add a license?
+You incorporate MIT, GPL, and BSD3 licensed code into your project. What possible licenses can you pick for your project?
+You do the same as above but add in another license that looks strong copyleft. What possible licenses can you use now?
+Do licenses apply if you don’t distribute your code? Why or why not?
+Which licenses are most/least attractive for companies with proprietary software?
+
+
+
Solution
+
+As indicated here , all publicly accessible user contributions are licensed under Creative Commons Attribution-ShareAlike license. See Stackoverflow Terms of service for more detailed information.
+“Standard” licensing rules apply. So in this case, you would need to remove the portion of code you have copied from another package before being able to release your software.
+By default you are no authorized to use the content of a repository when there is no license. And derivative work is also not possible by default. Other risks: it may not be clear whether you can use and distribute (publish) the bugfixed code. For the repo owners it may not be clear whether they can use and distributed the bugfixed code. However, the authors may have forgotten to add a license so we suggest you to contact the authors (e.g. make an issue) and ask whether they are willing to add a license.
+As mentionned in 3., the easiest is to fill an issue and explain the reasons why you would like to use this software (or update it).
+Combining software with different licenses can be tricky and it is important to understand compatibilities (or lack of compatibilities) of the various licenses. GPL license is the most protective (BSD and MIT are quite permissive) so for the resulting combined software you could use a GPL license. However, re-licensing may not be necessary.
+Derivative work would need to be shared under this strong copyleft license (e.g. AGPL or GPL), unless the components are only plugins or libraries.
+If you keep your code for yourself, you may think you do not need a license. However, remember that in most companies/universities, your employer is “owning” your work and when you leave you may not be allowed to “distribute your code to your future self”. So the best is always to add a license!
+The least attractive licenses for companies with proprietary software are licenses where you would need to keep an open license when creating derivative work. For instance GPL and and AGPL. The most attractive licenses are permissive licenses where they can reuse, modify and relicense with no conditions. For instance MIT, BSD and Apache License.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/testing/index.html b/branch/main/testing/index.html
new file mode 100644
index 0000000..6ae618d
--- /dev/null
+++ b/branch/main/testing/index.html
@@ -0,0 +1,385 @@
+
+
+
+
+
+
+
+
+ Automated testing — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Automated testing
+
+
Objectives
+
+Know where to start in your own project.
+Know what possibilities and techniques are available in the Python world.
+Have an example for how to make the testing part of code review .
+
+
+
+
Instructor note
+
+(15 min) Motivation
+(15 min) End-to-end tests
+(15 min) Pytest
+(15 min) Adding the unit test to GitHub Actions
+(10 min) What else is possible
+(20 min) Exercise
+
+
+
+Motivation
+Testing is a way to check that the code does what it is expected to.
+
+Less scary to change code : tests will tell you whether something broke.
+Easier for new people to join.
+Easier for somebody to revive an old code .
+End-to-end test : run the whole code and compare result to a reference.
+Unit tests : test one unit (function or module). Can guide towards better
+structured code: complicated code is more difficult to test.
+
+
+
+How testing is often taught
+def add ( a , b ):
+ return a + b
+
+
+def test_add ():
+ assert add ( 1 , 2 ) == 3
+
+
+How this feels:
+
+
+
+
+[Citation needed]
+
+
+Instead, we will look at and discuss a real example where we test components
+from our example project.
+
+
+Where to start
+Do I even need testing? :
+
+If you have nothing yet :
+
+Start with an end-to-end test.
+Describe in words how you check whether the code still works.
+Translate the words into a script (any language).
+Run the script automatically on every code change (GitHub Actions or GitLab CI).
+
+If you want to start with unit-testing :
+
+You want to rewrite a function? Start adding a unit test right there first.
+You spend few days chasing a bug? Once you fix it, add a test to make sure it does not come back.
+
+
+
+
+Pytest
+Here is a simple example of a test:
+def fahrenheit_to_celsius ( temp_f ):
+ """Converts temperature in Fahrenheit
+ to Celsius.
+ """
+ temp_c = ( temp_f - 32.0 ) * ( 5.0 / 9.0 )
+ return temp_c
+
+
+# this is the test function
+def test_fahrenheit_to_celsius ():
+ temp_c = fahrenheit_to_celsius ( temp_f = 100.0 )
+ expected_result = 37.777777
+ # assert raises an error if the condition is not met
+ assert abs ( temp_c - expected_result ) < 1.0e-6
+
+
+To run the test(s):
+
+Explanation: pytest
will look for functions starting with test_
in files
+and directories given as arguments. It will run them and report the results.
+Good practice to add unit tests:
+
+Add the test function and run it.
+Break the function on purpose and run the test.
+Does the test fail as expected?
+
+
+
+Adding the unit test to GitHub Actions
+Our next goal is that we want GitHub to run the unit test
+automatically on every change.
+First we need to extend our
+environment.yml :
+name : classification-task
+channels :
+ - conda-forge
+dependencies :
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - pytest
+
+
+We also need to extend .github/workflows/test.yml
(highlighted line):
+name : Test
+
+on :
+ push :
+ branches : [ main ]
+ pull_request :
+ branches : [ main ]
+
+jobs :
+ build :
+ runs-on : ubuntu-24.04
+
+ steps :
+ - name : Checkout
+ uses : actions/checkout@v4
+
+ - uses : mamba-org/setup-micromamba@v1
+ with :
+ micromamba-version : '2.0.5-0' # any version from https://github.com/mamba-org/micromamba-releases
+ environment-file : environment.yml
+ init-shell : bash
+ cache-environment : true
+ post-cleanup : 'all'
+ generate-run-shell : false
+
+ - name : Run tests
+ run : |
+ ./test.sh
+ pytest generate-predictions.py
+ shell : bash -el {0}
+
+
+In the above example, we assume that we added a test function to generate-predictions.py
.
+If we have time, we can try to create a pull request which would break the
+code and see how the test fails.
+
+
+What else is possible
+
+Run the test set automatically on every code change:
+
+
+The testing above used example-based testing.
+Test coverage : how much of the code is traversed by tests?
+
+
+Property-based testing: generates arbitrary data matching your specification and checks that your guarantee still holds in that case.
+
+
+Snapshot-based testing: makes it easier to generate snapshots for regression tests.
+
+
+Mutation testing : tests pass -> change a line of code (make a mutant) -> test again and check whether all mutants get “killed”.
+
+
+
+
+
+Exercises
+
+
Exercise
+
Experiment with the example project and what we learned above or try it on
+the example project or on your own project :
+
+Add a unit test. If you are unsure where to start , you can try to move
+the majority
+vote
+into a separate function and write a test function for it.
+Try to run pytest locally.
+Check whether it fails when you break the corresponding function.
+Try to run it on GitHub Actions.
+Create a pull request which would break the code and see whether the automatic test would catch it.
+Try to design an end-to-end test for your project. Already the thought
+process can be very helpful.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/version-control/branching-and-committing/index.html b/branch/main/version-control/branching-and-committing/index.html
new file mode 100644
index 0000000..9f5479c
--- /dev/null
+++ b/branch/main/version-control/branching-and-committing/index.html
@@ -0,0 +1,452 @@
+
+
+
+
+
+
+
+
+ Creating branches and commits — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Creating branches and commits
+The first and most basic task to do in Git is record changes using
+commits. In this part, we will record changes in two
+ways: on a new branch (which supports multiple lines of work at once), and directly
+on the “main” branch (which happens to be the default branch here).
+
+
Objectives
+
+Record new changes to our own copy of the project.
+Understand adding changes in two separate branches.
+See how to compare different versions or branches.
+
+
+
+Background
+
+In the previous episode we have browsed an existing repository and saw commits
+and branches .
+Each commit is a snapshot of the entire project at a certain
+point in time and has a unique identifier (hash ) .
+A branch is a line of development, and the main
branch or master
branch
+are often the default branch in Git.
+A branch in Git is like a sticky note that is attached to a commit . When we add
+new commits to a branch, the sticky note moves to the new commit.
+Tags are a way to mark a specific commit as important, for example a release
+version. They are also like a sticky note, but they don’t move when new
+commits are added.
+
+
+
+
+
+What if two people, at the same time, make two different changes? Git
+can merge them together easily. Image created using https://gopherize.me/
+(inspiration ).
+
+
+
+
+Exercise: Creating branches and commits
+
+
+
+
+Illustration of what we want to achieve in this exercise.
+
+
+
+
Exercise: Practice creating commits and branches (20 min)
+
+First create a new branch and then either add a new file or modify an
+existing file and commit the change. Make sure that you now work on your
+copy of the example repository. In your new commit you can share a Git or
+programming trick you like or improve the documentation.
+In a new commit, modify the file again.
+Switch to the main
branch and create a commit there.
+Browse the network and locate the commits that you just created (“Insights” -> “Network”).
+Compare the branch that you created with the main
branch. Can you find an easy way to see the differences?
+Can you find a way to compare versions between two arbitrary commits in the repository?
+Try to rename the branch that you created and then browse the network again.
+Try to create a tag for one of the commits that you created (on GitHub,
+create a “release”).
+Optional: If this was too easy, you can try to create a new branch and on
+this branch work on one of these new features:
+
+The random seed is now set to a specific number (42). Make it possible to
+set the seed to another number or to turn off the seed setting via the command line interface.
+Move the code that does the majority vote to an own function.
+Write a test for the new majority vote function.
+The num_neighbors
in the code needs to be odd. Change the code so that it stops with an error message if an even number is given.
+Add type annotations to some functions.
+When calling the scatter_plot
function, call the function with named arguments.
+Add example usage to README.md.
+Add a Jupyter Notebook version of the example code.
+
+
+
+
+The solution below goes over most of the answers, and you are
+encouraged to use it when the hints aren’t enough - this is by
+design.
+
+
+Solution and walk-through
+
+(1) Create a new branch and a new commit
+
+
GitHub VS Code Command line
+Where it says “main” at the top left, click, enter a new branch
+name (e.g. new-tutorial
), then click on
+“Create branch … from main”.
+Make sure you are still on the new-tutorial
branch (it should say
+it at the top), and click “Add file” → “Create new file” from the
+upper right.
+Enter a filename where it says “Name your file…”.
+Share some Git or programming trick you like.
+Click “Commit changes”
+Enter a commit message. Then click “Commit
+changes”.
+
+
You should appear back at the file browser view, and see your
+modification there.
+
+Make sure that you are on the main branch.
+Version control button on left sidebar → Three dots in upper right of source control → Branch → Create branch.
+VS Code automatically switches to the new branch.
+Create a new file.
+In the version control sidebar, click the +
sign to add the file for the next commit.
+Enter a brief message and click “Commit”.
+
+
Create a new branch called new-tutorial
from main
and switch to it:
+
$ git switch --create new-tutorial main
+
+
+
Then create the new file. Finally add and commit the file:
+
$ git add tutorial.md # or a different file name
+$ git commit -m "sharing a programming trick"
+
+
+
+
+
+(2) Modify the file again with a new commit
+
+
GitHub VS Code Command line
This is similar to before, but we click on the existing file to
+modify.
+
+Click on the file you added or modified previously.
+Click the edit button, the pencil icon at top-right.
+Follow the “Commit changes” instructions as in the previous step.
+
+
Repeat as in the previous step.
+
Modify the file. Then commit the new change:
+
$ git add tutorial.md
+$ git commit -m "short summary of the change"
+
+
+
Make sure to replace “short summary of the change” with a meaningful commit
+message.
+
+
+
+(3) Switch to the main branch and create a commit there
+
+
GitHub VS Code Command line
+Go back to the main repository page (your user’s page).
+In the branch switch view (top left above the file view), switch to
+main
.
+Modify another file that already exists, following the pattern
+from above.
+
+
Use the branch selector at the bottom to switch back to the main branch. Repeat the same steps as above,
+but this time modify a different file.
+
First switch to the main
branch:
+
+
Then modify a file. Finally git add
and then commit the change:
+
$ git commit -m "short summary of the change"
+
+
+
+
+
+(4) Browse the commits you just made
+Let’s look at what we did. Now, the main
and the new branches
+have diverged: both have some modifications. Try to find the commits
+you created.
+
+
GitHub VS Code Command line
Insights tab → Network view (just like we have done before).
+
This requires an extension. Opening the VS Code terminal lets you use the
+command line method (View → Terminal will open a terminal at bottom). This is
+a normal command line interface and very useful for work.
+
$ git graph
+$ git log --graph --oneline --decorate --all # if you didn't define git graph yet.
+
+
+
+
+
+(5) Compare the branches
+Comparing changes is an important thing we need to do. When using the
+GitHub view only, this may not be so common, but we’ll show it so that
+it makes sense later on.
+
+
GitHub VS Code Command line
A nice way to compare branches is to add /compare
to the URL of the repository,
+for example (replace USER):
+https://github.com/USER /classification-task/compare
+
This seems to require an extension. We recommend you use the command line method.
+
$ git diff main new-tutorial
+
+
+
Try also the other way around:
+
$ git diff new-tutorial main
+
+
+
Try also this if you only want to see the file names that are different:
+
$ git diff --name-only main new-tutorial
+
+
+
+
+
+(6) Compare two arbitrary commits
+This is similar to above, but not only between branches.
+
+
GitHub VS Code Command line
Following the /compare
-trick above, one can compare commits on GitHub by
+adjusting the following URL:
+https://github.com/USER /classification-task/compare/VERSION1 ..VERSION2
+
Replace USER
with your username and VERSION1
and VERSION2
with a commit hash or branch name.
+Please try it out.
+
Again, we recommend using the Command Line method.
+
First try this to get a short overview of the commits:
+
+
Then try to compare any two commit identifiers with git diff
.
+
+
+
+(7) Renaming a branch
+
+
GitHub VS Code Command line
Branch button → View all branches → three dots at right side → Rename branch.
+
Version control sidebar → Three dots (same as in step 2) → Branch → Rename branch. Make sure you are on the right branch before you start.
+
Renaming the current branch:
+
$ git branch -m new-branch-name
+
+
+
Renaming a different branch:
+
$ git branch -m different-branch new-branch-name
+
+
+
+
+
+(8) Creating a tag
+Tags are a way to mark a specific commit as important, for example a release
+version. They are also like a sticky note, but they don’t move when new
+commits are added.
+
+
GitHub VS Code Command line
On the right side, below “Releases”, click on “Create a new release”.
+
What GitHub calls releases are actually tags in Git with additional metadata.
+For the purpose of this exercise we can use them interchangeably.
+
Version control sidebar → Three dots (same as in step 2) → Tags → Create tag. Make sure you are on the expected commit before you do this.
+
Creating a tag:
+
$ git tag -a v1.0 -m "New manuscript version for the pre-print"
+
+
+
+
+
+
+Summary
+In this part, we saw how we can make changes to our files. With branches, we
+can track several lines of work at once, and can compare their differences.
+
+You could commit directly to main
if there is only one single line
+of work and it’s only you.
+You could commit to branches if there are multiple lines of work at
+once, and you don’t want them to interfere with each other.
+Tags are useful to mark a specific commit as important, for example a
+release version.
+In Git, commits form a so-called “graph”. Branches are tags in Git function
+like sticky notes that stick to specific commits. What this means for us is
+that it does not cost any significant disk space to create new branches.
+Not all files should be added to Git. For example, temporary files or
+files with sensitive information or files which are generated as part of
+the build process should not be added to Git. For this we use
+.gitignore
(more about this later: Practical advice: How much Git is necessary? ).
+Unsure on which branch you are or what state the repository is in?
+On the command line, use git status
frequently to get a quick overview.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/version-control/browsing/index.html b/branch/main/version-control/browsing/index.html
new file mode 100644
index 0000000..5eaf387
--- /dev/null
+++ b/branch/main/version-control/browsing/index.html
@@ -0,0 +1,467 @@
+
+
+
+
+
+
+
+
+ Forking, cloning, and browsing — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Forking, cloning, and browsing
+In this episode, we will look at an existing repository to understand how
+all the pieces work together. Along the way, we will make a copy (by
+forking and/or cloning ) of the repository for us, which will be used
+for our own changes.
+
+
Objectives
+
+See a real Git repository and understand what is inside of it.
+Understand how version control allows advanced inspection of a
+repository.
+See how Git allows multiple people to work on the same project at the same time.
+See the big picture instead of remembering a bunch of commands.
+
+
+
+GitHub, VS Code, or command line
+We offer three different paths for this exercise:
+
+GitHub (this is the one we will demonstrate)
+VS Code (if you prefer to follow along using an editor)
+Command line (for people comfortable with the command line)
+
+
+
+Creating a copy of the repository by “forking” or “cloning”
+A repository is a collection of files in one directory tracked by Git. A
+GitHub repository is GitHub’s copy, which adds things like access control,
+issue tracking, and discussions. Each GitHub repository is owned by a user or
+organization, who controls access.
+First, we need to make our own copy of the exercise repository. This will
+become important later, when we make our own changes.
+
+
+
+
+Illustration of forking a repository on GitHub.
+
+
+
+
+
+
+Illustration of cloning a repository to your computer.
+
+
+
+
+
+
+It is also possible to do this: to clone a forked repository to your computer.
+
+
+At all times you should be aware of if you are looking at your repository
+or the upstream repository (original repository):
+
+
+
How to create a fork
+
+Go to the repository view on GitHub: https://github.com/workshop-material/classification-task
+First, on GitHub, click the button that says “Fork”. It is towards
+the top-right of the screen.
+You should shortly be redirected to your copy of the repository
+USER/classification-task .
+
+
+
+
Instructor note
+
Before starting the exercise session show
+how to fork the repository to own account
+(above).
+
+
+
+Exercise: Copy and browse an existing project
+Work on this by yourself or in pairs.
+
+
Exercise preparation
+
+
GitHub VS Code Command line
In this case you will work on a fork.
+
You only need to open your own view, as described above. The browser
+URL should look like https://github.com/USER /classification-task, where
+USER is your GitHub username.
+
You need to have forked the repository as described above.
+
We need to start by making a clone of this repository so that you can work
+locally.
+
+Start VS Code.
+If you don’t have the default view (you already have a project
+open), go to File → New Window.
+Under “Start” on the screen, select “Clone Git Repository…”. Alternatively
+navigate to the “Source Control” tab on the left sidebar and click on the “Clone Repository” button.
+Paste in this URL: https://github.com/USER/classification-task
, where
+USER
is your username. You can copy this from the browser.
+Browse and select the folder in which you want to clone the
+repository.
+Say yes, you want to open this repository.
+Select “Yes, I trust the authors” (the other option works too).
+
+
This path is advanced and we do not include all command line
+information: you need to be somewhat
+comfortable with the command line already.
+
You need to have forked the repository as described above.
+
We need to start by making a clone of this repository so that you can work
+locally.
+
+Start the terminal in which you use Git (terminal application, or
+Git Bash).
+Change to the directory where you would want the repository to be
+(cd ~/code
for example, if the ~/code
directory is where you
+store your files).
+Run the following command: git clone https://github.com/USER/classification-task
, where USER
is your
+username. You might need to use a SSH clone command instead of
+HTTPS, depending on your setup.
+Change to that directory: cd classification-task
+
+
+
+
+
Exercise: Browsing an existing project (20 min)
+
Browse the example project and
+explore commits and branches, either on a fork or on a clone. Take notes and
+prepare questions. The hints are for the GitHub path in the browser.
+
+Browse the commit history : Are commit messages understandable?
+(Hint: “Commit history”, the timeline symbol, above the file list)
+Compare the commit history with the network graph (“Insights” -> “Network”). Can you find the branches?
+Try to find the history of commits for a single file , e.g. generate_predictions.py
.
+(Hint: “History” button in the file view)
+Which files include the word “training” ?
+(Hint: the GitHub search on top of the repository view)
+In the generate_predictions.py
file,
+find out who modified the evaluation of “majority_index”
+last and in which commit .
+(Hint: “Blame” view in the file view)
+Can you use this code yourself? Are you allowed to share
+modifications ?
+(Hint: look for a license file)
+
+
+The solution below goes over most of the answers, and you are
+encouraged to use it when the hints aren’t enough - this is by
+design.
+
+
+Solution and walk-through
+
+(1) Basic browsing
+The most basic thing to look at is the history of commits.
+
+This is visible from a button in the repository view. We see every
+change, when, and who has committed.
+Every change has a unique identifier, such as 79ce3be
. This can
+be used to identify both this change, and the whole project’s
+version as of that change.
+Clicking on a change in the view shows more.
+
+
+
GitHub VS Code Command line
Click on the timeline symbol in the repository view:
+
+
+
+
+
This can be done from “Timeline”, in the bottom of explorer, but only
+for a single file.
+
+
+
+(2) Compare commit history with network graph
+The commit history we saw above looks linear: one commit after
+another. But if we look at the network view, we see some branching and merging points.
+We’ll see how to do these later. This is another one of the
+basic Git views.
+
+
GitHub VS Code Command line
In a new browser tab, open the “Insights” tab, and click on “Network”.
+You can hover over the commit dots to see the person who committed and
+how they correspond with the commits in the other view:
+
+
+
+
+
We don’t know how to do this without an extension. Try starting a terminal and using the
+“Command line” option.
+
This is a useful command to browse the network of commits locally:
+
$ git log --graph --oneline --decorate --all
+
+
+
To avoid having to type this long command every time, you can define an alias (shortcut):
+
$ git config --global alias.graph "log --graph --oneline --decorate --all"
+
+
+
From then on, you can use git graph
to see the network graph.
+
+
+
+(3) How can you browse the history of a single file?
+We see the history for the whole repository, but we can also see it
+for a single file.
+
+
GitHub VS Code Command line
Navigate to the file view: Main page → generate_predictions.py.
+Click the “History” button near the top right.
+
Open generate_predictions.py file in the editor. Under the file browser,
+we see a “Timeline” view there.
+
The git log
command can take a filename and provide the log of only
+a single file:
+
$ git log generate_predictions.py
+
+
+
+
+
+(4) Which files include the word “training”?
+Version control makes it very easy to find all occurrences of a
+word or pattern. This is useful for things like finding where functions or
+variables are defined or used.
+
+
GitHub VS Code Command line
We go to the main file view. We click the Search magnifying
+class at the very top, type “training”, and click enter. We see every
+instance, including the context.
+
+
Searching in a forked repository will not work instantaneously!
+
It usually takes a few minutes before one can search for keywords in a forked repository
+since it first needs to build the search index the very first time we search.
+Start it, continue with other steps, then come back to this.
+
+
If you use the “Search” magnifying class on the left sidebar, and
+search for “training” it shows the occurrences in every file. You can
+click to see the usage in context.
+
grep
is the command line tool that searches for lines matching a term
+
$ git grep training # only the lines
+$ git grep -C 3 training # three lines of context
+$ git grep -i training # case insensitive
+
+
+
+
+
+(5) Who modified a particular line last and when?
+This is called the “annotate” or “blame” view. The name “blame”
+is very unfortunate, but it is the standard term for historical reasons
+for this functionality and it is not meant to blame anyone.
+
+
GitHub VS Code Command line
From a file view, change preview to “Blame” towards the top-left.
+To get the actual commit, click on the commit message next
+to the code line that you are interested in.
+
This requires an extension. We recommend for now you use the command
+line version, after opening a terminal.
+
These two commands are similar but have slightly different output.
+
$ git annotate generate_predictions.py
+$ git blame generate_predictions.py
+
+
+
+
+
+(6) Can you use this code yourself? Are you allowed to share modifications?
+
+Look at the file LICENSE
.
+On GitHub, click on the file to see a nice summary of what we can do with this:
+
+
+
+
+
+
+
+
+Summary
+
+Git allowed us to understand this simple project much better than we
+could, if it was just a few files on our own computer.
+It was easy to share the project with the course.
+By forking the repository, we created our own copy. This is
+important for the following, where we will make changes to
+our copy.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/version-control/conflict-resolution/index.html b/branch/main/version-control/conflict-resolution/index.html
new file mode 100644
index 0000000..588c715
--- /dev/null
+++ b/branch/main/version-control/conflict-resolution/index.html
@@ -0,0 +1,239 @@
+
+
+
+
+
+
+
+
+ Conflict resolution — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Conflict resolution
+
+Resolving a conflict (demonstration)
+A conflict is when Git asks humans to decide during a merge which of two
+changes to keep if the same portion of a file has been changed in two
+different ways on two different branches .
+We will practice conflict resolution in the collaborative Git lesson (next
+day).
+Here we will only demonstrate how to create a conflict and how to resolve it,
+all on GitHub . Once we understand how this works, we will be more confident
+to resolve conflicts also in the command line (we can demonstrate this if
+we have time).
+How to create a conflict (please try this in your own time and just watch now ):
+
+Create a new branch from main
and on it make a change to a file.
+On main
, make a different change to the same part of the same file.
+Now try to merge the new branch to main
. You will get a conflict.
+
+How to resolve conflicts:
+
+On GitHub, you can resolve conflicts by clicking on the “Resolve conflicts”
+button. This will open a text editor where you can choose which changes to
+keep.
+Make sure to remove the conflict markers.
+After resolving the conflict, you can commit the changes and merge the
+pull request.
+Sometimes a conflict is between your change and somebody else’s change. In
+that case, you might have to discuss with the other person which changes to
+keep.
+
+
+
+Avoiding conflicts
+
+
The human side of conflicts
+
+What does it mean if two people do the same thing in two different ways?
+What if you work on the same file but do two different things in the different sections?
+What if you do something, don’t tell someone from 6 months, and then try to combine it with other people’s work?
+How are conflicts avoided in other work? (Only one person working at once?
+Declaring what you are doing before you start, if there is any chance someone
+else might do the same thing, helps.)
+
+
+
+Human measures
+
+
+Collaboration measures
+
+
+Project layout measures
+
+
+Technical measures
+
+Share your changes early and often : This is one of the happy,
+rare circumstances when everyone doing the selfish thing (publishing your
+changes as early as practical) results in best case for everyone!
+Pull/rebase often to keep up to date with upstream.
+Resolve conflicts early.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/version-control/index.html b/branch/main/version-control/index.html
new file mode 100644
index 0000000..5d71f6b
--- /dev/null
+++ b/branch/main/version-control/index.html
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+ Introduction to version control with Git and GitHub — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/version-control/merging/index.html b/branch/main/version-control/merging/index.html
new file mode 100644
index 0000000..40ff23b
--- /dev/null
+++ b/branch/main/version-control/merging/index.html
@@ -0,0 +1,447 @@
+
+
+
+
+
+
+
+
+ Merging changes and contributing to the project — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Merging changes and contributing to the project
+Git allows us to have different development lines where we can try things out.
+It also allows different people to work on the same project at the same. This
+means that we have to somehow combine the changes later. In this part we will
+practice this: merging .
+
+
Objectives
+
+Understand that on GitHub merging is done through a pull request (on GitLab: “merge request”). Think
+of it as a change proposal .
+Create and merge a pull request within your own repository.
+Understand (and optionally) do the same across repositories, to contribute to
+the upstream public repository.
+
+
+
+Exercise
+
+
+
+
+Illustration of what we want to achieve in this exercise.
+
+
+
+
Exercise: Merging branches
+
+
GitHub Local (VS Code, Command line)
First, we make something called a pull request, which allows
+review and commenting before the actual merge.
+
We assume that in the previous exercise you have created a new branch with one
+or few new commits. We provide basic hints. You should refer to the solution
+as needed.
+
+Navigate to your branch from the previous episode
+(hint: the same branch view we used last time).
+Begin the pull request process
+(hint: There is a “Contribute” button in the branch view).
+Add or modify the pull request title and description, and verify the other data.
+In the pull request verify the target repository and the target
+branch. Make sure that you are merging within your own repository.
+GitHub: By default, it will offer to make the change to the
+upstream repository, workshop-material
. You should change this , you
+shouldn’t contribute your commit(s) upstream yet. Where it says
+base repository
, select your own repository.
+Create the pull request by clicking “Create pull request”. Browse
+the network view to see if anything has changed yet.
+Merge the pull request, or if you are not on GitHub you can merge
+the branch locally. Browse the network again. What has changed?
+Find out which branches are merged and thus safe to delete. Then remove them
+and verify that the commits are still there, only the branch labels are
+gone (hint: you can delete branches that have been merged into main
).
+Optional: Try to create a new branch with a new change, then open a pull
+request but towards the original (upstream) repository. We will later merge few of
+those.
+
+
When working locally, it’s easier to merge branches: we can just do
+the merge, without making a pull request. But we don’t have that step
+of review and commenting and possibly adjusting.
+
+Switch to the main
branch that you want to merge the other
+branch into. (Note that this is the other way around from the
+GitHub path).
+
+
Then:
+
+Merge the other branch into main
(which is then your current branch).
+Find out which branches are merged and thus safe to delete. Then remove them
+and verify that the commits are still there, only the branch labels are
+gone. (Hint: you can delete branches that have been merged into main
).
+Optional: Try to create a new branch, and make a
+GitHub pull request with your change, and contribute it to our
+upstream repository.
+
+
+
+The solution below goes over most of the answers, and you are
+encouraged to use it when the hints aren’t enough - this is by design.
+
+
+Solution and walk-through
+
+(1) Navigate to your branch
+Before making the pull request, or doing a merge, it’s important to
+make sure that you are on the right branch. Many people have been
+frustrated because they forgot this!
+
+
GitHub VS Code Command line
GitHub will notice a recently changed branch and offer to make a pull request (clicking there will bring you to step 3):
+
+
+
+
+
If the yellow box is not there, make sure you are on the branch you want to
+merge from :
+
+
+
+
+
Remember, you need to switch to the main
branch, the branch we want to merge to .
+This is different from the GitHub path.
+
Remember, you need to switch to the main
branch, the branch we want to merge to .
+This is different from the GitHub path:
+
+
+
+
+(2) Begin the pull request process
+In GitHub, the pull request is the way we propose to merge two
+branches together. We start the process of making one.
+
+
GitHub VS Code Command line
+
+
+
+
It is possible to open pull requests from the editor, but
+we don’t cover that here.
+
If you are working locally, continue to step 5.
+
It is possible to open pull requests from the command line, but
+we don’t cover that here.
+
If you are working locally, continue to step 5.
+
+
+
+(3) Fill out and verify the pull request
+Check that the pull request is directed to the right repository and branch
+and that it contains the changes that you meant to merge.
+
+
GitHub VS Code Command line
Things to check:
+
+Base repository: this should be your own
+Title: make it descriptive
+Description: make it informative
+Scroll down to see commits: are these the ones you want to merge?
+Scroll down to see the changes: are these the ones you want to merge?
+
+
+
+
+This screenshot only shows the top part. If you scroll down, you
+can see the commits and the changes. We recommend to do this before
+clicking on “Create pull request”.
+
+
+
+
+
If you are working locally, continue to step 5.
+
If you are working locally, continue to step 5.
+
+
+
+(4) Create the pull request
+We actually create the pull request. Don’t forget to navigate to the Network
+view after opening the pull request. Note that the changes proposed in the
+pull request are not yet merged.
+
+
GitHub VS Code Command line
Click on the green button “Create pull request”.
+
If you click on the little arrow next to “Create pull request”, you can also
+see the option to “Create draft pull request”. This will be interesting later
+when collaborating with others. It allows you to open a pull request that is
+not ready to be merged yet, but you want to show it to others and get feedback.
+
It is possible to create pull requests from the editor, but
+we don’t cover that here.
+
If you are working locally, continue to step 5.
+
It is possible to create pull requests from the command line, but
+we don’t cover that here.
+
If you are working locally, continue to step 5.
+
+
+
+(5) Merge the pull request
+Now, we do the actual merging. We see some effects now.
+
+
GitHub VS Code Command line
Review it again (commits and changes), and then click “Merge pull request”.
+
After merging, verify the network view. Also navigate then to your “main”
+branch and check that your change is there.
+
Just like with the command line, when we merge we modify our current branch. Verify you are on the main
branch.
+
+Verify current branch at the bottom.
+From the version control sidebar → Three dots → Branch → Merge.
+In the selector that comes up, choose the branch you want to merge from .
+The commits on that branch will be added to the current branch.
+
+
On the command line, when we merge, we always modify our current branch.
+
If you are not sure anymore what your current branch is, type:
+
+
… or equally useful to see where we are right now:
+
+
In this case we merge the new-tutorial
branch into our current branch:
+
$ git merge new-tutorial
+
+
+
+
+
+(6) Delete merged branches
+Before deleting branches, first check whether they are merged.
+If you delete an un-merged branch, it will be difficult to find the commits
+that were on that branch. If you delete a merged branch, the commits are now
+also part of the branch where we have merged to.
+
+
GitHub VS Code Command line
One way to delete the branch is to click on the “Delete branch” button after the pull
+request is merged:
+
+
+
+
+
But what if we forgot? Then navigate to the branch view:
+
+
+
+
+
In the overview we can see that it has been merged and we can delete it.
+
From the Source Control sidebar → the three dots (as before) → Branch → Delete Branch. Select the branch name to delete.
+
Verify which branches are merged to the current branch:
+
$ git branch --merged
+
+* main
+ new-tutorial
+
+
+
This means that it is safe to delete the new-tutorial
branch:
+
$ git branch -d new-tutorial
+
+
+
Verify then that the branch is gone but that the commits are still there:
+
$ git branch
+$ git log --oneline
+
+
+
+
+
+(7) Contribute to the original repository with a pull request
+This is an advanced step. We will practice this tomorrow and
+it is OK to skip this at this stage.
+
+
GitHub VS Code Command line
Now that you know how to create branches and opening a pull request, try to
+open a new pull request with a new change but this time the base repository
+should be the upstream one.
+
In other words, you now send a pull request across repositories : from your fork
+to the original repository.
+
Another thing that is different now is that you might not have permissions to
+merge the pull request. We can then together review and browse the pull
+request.
+
Not described. We will return to this when we discuss collaboration using GitHub.
+
You would create a new branch locally like above, push it to GitHub to your own
+user’s repository, and from there open a pull request towards the upstream
+repository.
+
Not described. We will return to this when we discuss collaboration using GitHub.
+
You would create a new branch locally like above, push it to GitHub to your own
+user’s repository, and from there open a pull request towards the upstream
+repository.
+
+
+
+
+Summary
+
+We learned how to merge two branches together.
+When is this useful? This is not only useful to combine development lines in
+your own work. Being able to merge branches also forms the basis for collaboration .
+Branches which are merged to other branches are safe to delete, since we only
+delete the “sticky note” next to a commit, not the commits themselves.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/version-control/motivation/index.html b/branch/main/version-control/motivation/index.html
new file mode 100644
index 0000000..9dced41
--- /dev/null
+++ b/branch/main/version-control/motivation/index.html
@@ -0,0 +1,287 @@
+
+
+
+
+
+
+
+
+ Motivation — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Motivation
+
+
Objectives
+
+Browse commits and branches of a Git repository.
+Remember that commits are like snapshots of the repository at a certain
+point in time.
+Know the difference between Git (something that tracks changes) and
+GitHub/GitLab (a web platform to host Git repositories).
+
+
+
+Why do we need to keep track of versions?
+Version control is an answer to the following questions (do you recognize some
+of them?):
+
+“It broke … hopefully I have a working version somewhere?”
+“Can you please send me the latest version?”
+“Where is the latest version?”
+“Which version are you using?”
+“Which version have the authors used in the paper I am trying to reproduce?”
+“Found a bug! Since when was it there?”
+“I am sure it used to work. When did it change?”
+“My laptop is gone. Is my thesis now gone?”
+
+
+
+Demonstration
+
+Example repository: https://github.com/workshop-material/classification-task
+Commits are like snapshots and if we break something we can go back to a
+previous snapshot.
+Commits carry metadata about changes: author, date, commit message, and
+a checksum.
+Branches are like parallel universes where you can experiment with
+changes without affecting the default branch:
+https://github.com/workshop-material/classification-task/network
+(“Insights” -> “Network”)
+With version control we can annotate code
+(example ).
+Collaboration : We can fork (make a copy on GitHub), clone (make a copy
+to our computer), review, compare, share, and discuss.
+Code review : Others can suggest changes using pull requests or merge
+requests. These can be reviewed and discussed before they are merged.
+Conceptually, they are similar to “suggesting changes” in Google Docs.
+
+
+
+
+
+What we typically like to snapshot
+
+Software (this is how it started but Git/GitHub can track a lot more)
+Scripts
+Documents (plain text files much better suitable than Word documents)
+Manuscripts (Git is great for collaborating/sharing LaTeX or Quarto manuscripts)
+Configuration files
+Website sources
+Data
+
+
+
Discussion
+
In this example somebody tried to keep track of versions without a version
+control system tool like Git. Discuss the following directory listing. What
+possible problems do you anticipate with this kind of “version control”:
+
myproject-2019.zip
+myproject-2020-february.zip
+myproject-2021-august.zip
+myproject-2023-09-19-working.zip
+myproject-2023-09-21.zip
+myproject-2023-09-21-test.zip
+myproject-2023-09-21-myversion.zip
+myproject-2023-09-21-newfeature.zip
+...
+(100 more files like these)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/version-control/practical-advice/index.html b/branch/main/version-control/practical-advice/index.html
new file mode 100644
index 0000000..2d9b1cd
--- /dev/null
+++ b/branch/main/version-control/practical-advice/index.html
@@ -0,0 +1,303 @@
+
+
+
+
+
+
+
+
+ Practical advice: How much Git is necessary? — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Practical advice: How much Git is necessary?
+
+Writing useful commit messages
+Useful commit messages summarize the change and provide context .
+If you need a commit message that is longer than one line,
+then the convention is: one line summarizing the commit, then one empty line,
+then paragraph(s) with more details in free form, if necessary.
+Good example:
+increase alpha to 2.0 for faster convergence
+
+the motivation for this change is
+to enable ...
+...
+(more context)
+...
+this is based on a discussion in #123
+
+
+
+Why something was changed is more important than what has changed.
+Cross-reference to issues and discussions if possible/relevant.
+Bad commit messages: “fix”, “oops”, “save work”
+Just for fun, a page collecting bad examples: http://whatthecommit.com
+Write commit messages that will be understood
+15 years from now by someone else than you. Or by your future you.
+Many projects start out as projects “just for me” and end up to be successful projects
+that are developed by 50 people over decades.
+Commits with multiple authors are possible.
+
+Good references:
+
+
+
Note
+
A great way to learn how to write commit messages and to get inspired by their
+style choices: browse repositories of codes that you use/like :
+
Some examples (but there are so many good examples):
+
+
When designing commit message styles consider also these:
+
+How will you easily generate a changelog or release notes?
+During code review, you can help each other improving commit messages.
+
+
+But remember: it is better to make any commit, than no commit. Especially in small projects.
+Let not the perfect be the enemy of the good enough .
+
+
+What level of branching complexity is necessary for each project?
+Simple personal projects :
+
+Typically start with just the main
branch.
+Use branches for unfinished/untested ideas.
+Use branches when you are not sure about a change.
+Use tags to mark important milestones.
+If you are unsure what to do with unfinished and not working code, commit it
+to a branch.
+
+Projects with few persons: you accept things breaking sometimes
+
+Projects with few persons: changes are reviewed by others
+
+You create new feature branches for changes.
+Changes are reviewed before they are merged to the main
branch.
+Consider to write-protect the main
branch so that it can only be changed
+with pull requests or merge requests.
+
+
+
+How large should a commit be?
+
+Better too small than too large (easier to combine than to split).
+Often I make a commit at the end of the day (this is a unit I would not like to lose).
+Smaller sized commits may be easier to review for others than huge commits.
+A commit should not contain unrelated changes to simplify review and possible
+repair/adjustments/undo later (but again: imperfect commits are better than no commits).
+Imperfect commits are better than no commits.
+
+
+
+Working on the command line? Use “git status” all the time
+The git status
command is one of the most useful commands in Git
+to inform about which branch we are on, what we are about to commit,
+which files might not be tracked, etc.
+
+
+How about staging and committing?
+
+Commit early and often: rather create too many commits than too few.
+You can always combine commits later.
+Once you commit, it is very, very hard to really lose your code.
+Always fully commit (or stash) before you do dangerous things, so that you know you are safe.
+Otherwise it can be hard to recover.
+Later you can start using the staging area (where you first stage and then commit in a second step).
+Later start using git add -p
and/or git commit -p
.
+
+
+
+What to avoid
+
+Committing generated files/directories (example: __pycache__
, *.pyc
) ->
+use .gitignore
+files
+(collection of .gitignore templates ).
+Committing huge files -> use code review to detect this.
+Committing unrelated changes together.
+Postponing commits because the changes are “unfinished”/”ugly” -> better ugly
+commits than no commits.
+When working with branches:
+
+Working on unrelated things on the same branch.
+Not updating your branch before starting new work.
+Too ambitious branch which risks to never get completed.
+Over-engineering the branch layout and safeguards in small projects -> can turn people away from your project.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/branch/main/version-control/sharing/index.html b/branch/main/version-control/sharing/index.html
new file mode 100644
index 0000000..a7a973b
--- /dev/null
+++ b/branch/main/version-control/sharing/index.html
@@ -0,0 +1,422 @@
+
+
+
+
+
+
+
+
+ Optional: How to turn your project to a Git repo and share it — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Optional: How to turn your project to a Git repo and share it
+
+
Objectives
+
+Turn our own coding project (small or large, finished or unfinished) into a
+Git repository.
+Be able to share a repository on the web to have a backup or so that others
+can reuse and collaborate or even just find it.
+
+
+
+Exercise
+
+
+
+
+From a bunch of files to a local repository which we then share on GitHub.
+
+
+
+
Exercise: Turn your project to a Git repo and share it (20 min)
+
+Create a new directory called myproject (or a different name) with one or few files in it.
+This represents our own project. It is not yet a Git repository. You can try
+that with your own project or use a simple placeholder example.
+Turn this new directory into a Git repository.
+Share this repository on GitHub (or GitLab, since it really works the same).
+
+
We offer three different paths of how to do this exercise.
+
+Via GitHub web interface : easy and can be a good starting point if you are completely
+new to Git.
+VS Code is quite easy, since VS Code can offer to create the
+GitHub repositories for you.
+Command line : you need to create the
+repository on GitHub and link it yourself.
+
+
+
Only using GitHub VS Code Command line RStudio
Create an repository on GitHub
+
First log into GitHub, then follow the screenshots and descriptions below.
+
+
+
+
+Click on the “plus” symbol on top right, then on “New repository”.
+
+
+
Then:
+
+
+
+
+Choose a repository name, add a short description, and in this case make sure to check “Add a
+README file”. Finally “Create repository”.
+
+
+
Upload your files
+
Now that the repository is created, you can upload your files:
+
+
+
+
+Click on the “+” symbol and then on “Upload files”.
+
+
+
In VS Code it only takes few clicks.
+
First, open the folder in VS Code. My example project here contains two files.
+Then click on the source control icon:
+
+
+
+
+Open the folder in VS Code. Then click on the source control icon.
+
+
+
+
+
+
+Choose “Publish to GitHub”. In this case I never even clicked on “Initialize Repository”.
+
+
+
+
+
+
+In my case I chose to “Publish to GitHub public repository”. Here you can also rename
+the repository if needed.
+
+
+
+
+
+
+First time you do this you might need to authorize VS Code to access your
+GitHub account by redirecting you to https://github.com/login/oauth/authorize.
+
+
+
+
+
+
+After it is published, click on “Open on GitHub”.
+
+
+
Put your project under version control
+
My example project here consists of two files. Replace this with your own
+example files:
+
$ ls -l
+
+.rw------- 19k user 7 Mar 17:36 LICENSE
+.rw------- 21 user 7 Mar 17:36 myscript.py
+
+
+
I will first initialize a Git repository in this directory.
+If you get an error, try without the -b main
(and your default branch will
+then be called master
, this will happen for Git versions older than
+2.28):
+
+
Now add and commit the two files to the Git repository:
+
$ git add LICENSE myscript.py
+$ git commit -m "putting my project under version control"
+
+
+
If you want to add all files in one go, you can use git add .
instead of git add LICENSE myscript.py
.
+
Now you have a Git repository with one commit. Verify this with git log
.
+But it’s still only on your computer. Let’s put it on GitHub next.
+
Create an empty repository on GitHub
+
First log into GitHub, then follow the screenshots and descriptions below.
+
+
+
+
+Click on the “plus” symbol on top right, then on “New repository”.
+
+
+
Then create an empty repository without any files and without any commits:
+
+
+
+
+Choose a repository name, add a short description, but please do not check “Add a
+README file”. For “Add .gitignore” and “Choose a license” also leave as “None”. Finally “Create repository”.
+
+
+
Once you click the green “Create repository”, you will see a page similar to:
+
+
+
+
+
What this means is that we have now an empty project with either an HTTPS or an
+SSH address: click on the HTTPS and SSH buttons to see what happens.
+
Push an existing repository from your computer to GitHub
+
We now want to follow the “… or push an existing repository from the command line ”:
+
+In your terminal make sure you are still in your myproject directory.
+Copy paste the three lines below the red arrow to the terminal and execute
+those, in my case (you need to replace the “USER” part and possibly also
+the repository name ):
+
+
+
SSH HTTPS
$ git remote add origin git@github.com:USER/myproject.git
+
+
+
$ git remote add origin https://github.com/USER/myproject.git
+
+
+
+
Then:
+
$ git branch -M main
+$ git push -u origin main
+
+
+
The meaning of the above lines:
+
+Add a remote reference with the name “origin”
+Rename current branch to “main”
+Push branch “main” to “origin”
+
+
You should now see:
+
Enumerating objects: 4, done.
+Counting objects: 100% (4/4), done.
+Delta compression using up to 12 threads
+Compressing objects: 100% (3/3), done.
+Writing objects: 100% (4/4), 6.08 KiB | 6.08 MiB/s, done.
+Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
+To github.com:USER/myproject.git
+ * [new branch] main -> main
+branch 'main' set up to track 'origin/main'.
+
+
+
Reload your GitHub project website and your commits should now be
+online!
+
Troubleshooting
+
error: remote origin already exists
+
+
remote contains work that you do not have
+
+
This is not fully explained, because a lot of it is similar to the “Command
+line” method (and an RStudio expert could help us some). The main differences
+are:
+
Put your project under version control
+
+Tools → Version control → Project setup → Version control system = Git.
+Select “Yes” for “do you want to initialize a new git repository for this project.
+Select yes to restart the project with RStudio.
+Switch to branch main
to have you branch named that.
+
+
Create an empty repository on GitHub
+
Same as command line
+
Push an existing repository from your computer to GitHub
+
+Under the “Create new branch” button → “Add Remote”
+Remote name: origin
+Remote URL: as in command line (remember to select SSH or HTTPS as you have configured your RStudio)
+The “Push” (up arrow) button will send changes on your current branch to the remote. The “Pull” (down arrow) will get changes from the remote.
+
+
Troubleshooting
+
Same as command line
+
+
+
+
+Is putting software on GitHub/GitLab/… publishing?
+It is a good first step but to make your code truly findable and
+accessible , consider making your code citable and persistent : Get a
+persistent identifier (PID) such as DOI in addition to sharing the code
+publicly, by using services like Zenodo or similar
+services.
+More about this in How to publish your code .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/collaboration/code-review/index.html b/collaboration/code-review/index.html
new file mode 100644
index 0000000..655b993
--- /dev/null
+++ b/collaboration/code-review/index.html
@@ -0,0 +1,294 @@
+
+
+
+
+
+
+
+
+ Practicing code review — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Practicing code review
+In this episode we will practice the code review process. We will learn how to
+ask for changes in a pull request, how to suggest a change in a pull request,
+and how to modify a pull request.
+This will enable research groups to work more collaboratively and to not only
+improve the code quality but also to learn from each other .
+
+Exercise
+
+
Exercise preparation
+
We can continue in the same exercise repository which we have used in the
+previous episode.
+
+
+
Exercise: Practicing code review (25 min)
+
Technical requirements :
+
+
What is familiar from previous lessons:
+
+
What will be new in this exercise:
+
+As a reviewer, we will learn how to ask for changes in a pull request.
+As a reviewer, we will learn how to suggest a change in a pull request.
+As a submitter, we will learn how to modify a pull request without closing
+the incomplete one and opening a new one.
+
+
Exercise tasks :
+
+Create a new branch and one or few commits: in these improve something but also
+deliberately introduce a typo and also a larger mistake which we will want to fix during the code review.
+Open a pull request towards the main branch.
+As a reviewer to somebody else’s pull request, ask for an improvement and
+also directly suggest a change for the small typo. (Hint:
+suggestions are possible through the GitHub web interface, view of
+a pull request, “Files changed” view, after selecting some lines.
+Look for the “±” button.)
+As the submitter, learn how to accept the suggested change. (Hint:
+GitHub web interface, “Files Changed” view.)
+As the submitter, improve the pull request without having to close and open
+a new one: by adding a new commit to the same branch. (Hint: push
+to the branch again.)
+Once the changes are addressed, merge the pull request.
+
+
+
+
+Help and discussion
+From here on out, we don’t give detailed steps to the solution. You
+need to combine what you know, and the extra info below, in order to
+solve the above.
+
+How to ask for changes in a pull request
+Technically, there are at least two common ways to ask for changes in a pull
+request.
+Either in the comment field of the pull request:
+
+
+
+
+Or by using the “Review changes”:
+
+
+
+
+And always please be kind and constructive in your comments. Remember that the
+goal is not gate-keeping but collaborative learning .
+
+
+How to suggest a change in a pull request as a reviewer
+If you see a very small problem that is easy to fix, you can suggest a change
+as a reviewer.
+Instead of asking the submitter to tiny problem, you can suggest a change by
+clicking on the plus sign next to the line number in the “Files changed” tab:
+
+
+
+
+Here you can comment on specific lines or even line ranges.
+But now the interesting part is to click on the “Add a suggestion” symbol (the
+one that looks like plus and minus). Now you can fix the tiny problem (in this
+case a typo) and then click on the “Add single comment” button:
+
+
+
+
+The result is this and the submitter can accept the change with a single click:
+
+
+
+
+After accepting with “Commit suggestion”, the improvement gets added to the
+pull request.
+
+
+
+
+Summary
+
+Our process isn’t just about code now. It’s about discussion and
+working together to make the whole process better.
+GitHub (or GitLab) discussions and reviewing are quite powerful and can make
+small changes easy.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/collaboration/concepts/index.html b/collaboration/concepts/index.html
new file mode 100644
index 0000000..176d324
--- /dev/null
+++ b/collaboration/concepts/index.html
@@ -0,0 +1,235 @@
+
+
+
+
+
+
+
+
+ Concepts around collaboration — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Concepts around collaboration
+
+
+Commits, branches, repositories, forks, clones
+
+repository : The project, contains all data and history (commits, branches, tags).
+commit : Snapshot of the project, gets a unique identifier (e.g. c7f0e8bfc718be04525847fc7ac237f470add76e
).
+branch : Independent development line. The main development line is often called main
.
+tag : A pointer to one commit, to be able to refer to it later. Like a commemorative plaque
+that you attach to a particular commit (e.g. phd-printed
or paper-submitted
).
+cloning : Copying the whole repository to your laptop - the first time. It is not necessary to download each file one by one.
+forking : Taking a copy of a repository (which is typically not yours) - your
+copy (fork) stays on GitHub/GitLab and you can make changes to your copy.
+
+
+
+Cloning a repository
+In order to make a complete copy a whole repository, the git clone
command
+can be used. When cloning, all the files, of all or selected branches, of a
+repository are copied in one operation. Cloning of a repository is of relevance
+in a few different situations:
+
+Working on your own, cloning is the operation that you can use to create
+multiple instances of a repository on, for instance, a personal computer, a
+server, and a supercomputer.
+The parent repository could be a repository that you or your colleague own. A
+common use case for cloning is when working together within a smaller team
+where everyone has read and write access to the same git repository.
+Alternatively, cloning can be made from a public repository of a code that
+you would like to use. Perhaps you have no intention to work on the code, but
+would like to stay in tune with the latest developments, also in-between
+releases of new versions of the code.
+
+
+
+
+
+Forking and cloning
+
+
+
+
+Forking a repository
+When a fork is made on GitHub/GitLab a complete copy, of all or selected
+branches, of the repository is made. The copy will reside under a different
+account on GitHub/GitLab. Forking of a repository is of high relevance when
+working with a git repository to which you do not have write access.
+
+In the fork repository commits can be made to the base branch (main
or
+master
), and to other branches.
+The commits that are made within the branches of the fork repository can be
+contributed back to the parent repository by means of pull or merge requests.
+
+
+
+Synchronizing changes between repositories
+
+We need a mechanism to communicate changes between the repositories.
+We will pull or fetch updates from remote repositories (we will soon discuss the difference between pull and fetch).
+We will push updates to remote repositories.
+We will learn how to suggest changes within repositories on GitHub and across repositories (pull request ).
+Repositories that are forked or cloned do not automatically synchronize themselves:
+We will learn how to update forks (by pulling from the “central” repository).
+A main difference between cloning a repository and forking a repository is that the former is a general operation for generating copies of a repository to different computers, whereas forking is a particular operation implemented on GitHub/GitLab.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/collaboration/forking-workflow/index.html b/collaboration/forking-workflow/index.html
new file mode 100644
index 0000000..fbe568d
--- /dev/null
+++ b/collaboration/forking-workflow/index.html
@@ -0,0 +1,381 @@
+
+
+
+
+
+
+
+
+ How to contribute changes to repositories that belong to others — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+How to contribute changes to repositories that belong to others
+In this episode we prepare you to suggest and contribute changes to
+repositories that belong to others. These might be open source projects that
+you use in your work.
+We will see how Git and services like GitHub or GitLab can be used to suggest
+modification without having to ask for write access to the repository and
+accept modifications without having to grant write access to others.
+
+Exercise
+
+
Exercise preparation
+
+The exercise repository is now different:
+https://github.com/workshop-material/recipe-book-forking-exercise (note the -forking-exercise ).
+First fork the exercise repository to your GitHub account.
+Then clone your fork to your computer (if you wish to work locally).
+Double-check that you have forked the correct repository.
+
+
+
+
Exercise: Collaborating within the same repository (25 min)
+
Technical requirements :
+
+
What is familiar from previous lessons:
+
+
What will be new in this exercise:
+
+Opening a pull request towards the upstream repository.
+Pull requests can be coupled with automated testing.
+Learning that your fork can get out of date.
+After the pull requests are merged, updating your fork with the changes.
+Learn how to approach other people’s repositories with ideas, changes, and requests.
+
+
Exercise tasks :
+
+Open an issue in the upstream exercise repository where you describe the
+change you want to make. Take note of the issue number.
+Create a new branch in your fork of the repository.
+Make a change to the recipe book on the new branch and in the commit cross-reference the issue you opened.
+See the walk-through below for how to do this.
+Open a pull request towards the upstream repository.
+The instructor will review and merge the pull requests.
+During the review, pay attention to the automated test step (here for
+demonstration purposes, we test whether the recipe contains an ingredients
+and an instructions sections).
+After few pull requests are merged, update your fork with the changes.
+Check that in your fork you can see changes from other people’s pull requests.
+
+
+
+
+Help and discussion
+
+Help! I don’t have permissions to push my local changes
+Maybe you see an error like this one:
+ Please make sure you have the correct access rights
+and the repository exists.
+
+
+Or like this one:
+ failed to push some refs to workshop-material/recipe-book-forking-exercise.git
+
+
+In this case you probably try to push the changes not to your fork but to the original repository
+and in this exercise you do not have write access to the original repository.
+The simpler solution is to clone again but this time your fork.
+
+
Recovery
+
But if you want to keep your local changes, you can change the remote URL to point to your fork.
+Check where your remote points to with git remote --verbose
.
+
It should look like this (replace USER
with your GitHub username):
+
$ git remote --verbose
+
+origin git@github.com:USER/recipe-book-forking-exercise.git (fetch)
+origin git@github.com:USER/recipe-book-forking-exercise.git (push)
+
+
+
It should not look like this:
+
$ git remote --verbose
+
+origin git@github.com:workshop-material/recipe-book-forking-exercise.git (fetch)
+origin git@github.com:workshop-material/recipe-book-forking-exercise.git (push)
+
+
+
In this case you can adjust “origin” to point to your fork with:
+
$ git remote set-url origin git@github.com:USER/recipe-book-forking-exercise.git
+
+
+
+
+
+Opening a pull request towards the upstream repository
+We have learned in the previous episode that pull requests are always from
+branch to branch. But the branch can be in a different repository.
+When you open a pull request in a fork, by default GitHub will suggest to
+direct it towards the default branch of the upstream repository.
+This can be changed and it should always be verified, but in this case this is
+exactly what we want to do, from fork towards upstream:
+
+
+
+
+
+
+Pull requests can be coupled with automated testing
+We added an automated test here just for fun and so that you see that this is
+possible to do.
+In this exercise, the test is silly. It will check whether the recipe contains
+both an ingredients and an instructions section.
+In this example the test failed:
+
+
+
+
+Click on the “Details” link to see the details of the failed test:
+
+
+
+
+How can this be useful?
+
+The project can define what kind of tests are expected to pass before a pull
+request can be merged.
+The reviewer can see the results of the tests, without having to run them
+locally.
+
+How does it work?
+
+What tests or steps can you image for your project to run automatically with
+each pull request?
+
+
+How to update your fork with changes from upstream
+This used to be difficult but now it is two mouse clicks.
+Navigate to your fork and notice how GitHub tells you that your fork is behind.
+In my case, it is 9 commits behind upstream. To fix this, click on “Sync fork”
+and then “Update branch”:
+
+
+
+
+After the update my “branch is up to date” with the upstream repository:
+
+
+
+
+
+
+How to approach other people’s repositories with ideas, changes, and requests
+Contributing very minor changes
+
+If you observe an issue and have an idea how to fix it
+
+Open an issue in the repository you wish to contribute to
+Describe the problem
+If you have a suggestion on how to fix it, describe your suggestion
+Possibly discuss and get feedback
+If you are working on the fix, indicate it in the issue so that others know that somebody is working on it and who is working on it
+Submit your fix as pull request or merge request which references/closes the issue
+
+
+If you have an idea for a new feature
+
+Open an issue in the repository you wish to contribute to
+In the issue, write a short proposal for your suggested change or new feature
+Motivate why and how you wish to do this
+Also indicate where you are unsure and where you would like feedback
+Discuss and get feedback before you code
+Once you start coding, indicate that you are working on it
+Once you are done, submit your new feature as pull request or merge request which references/closes the issue/proposal
+
+
+
Motivation
+
+Get agreement and feedback before writing 5000 lines of code which might be rejected
+If we later wonder why something was done, we have the issue/proposal as
+reference and can read up on the reasoning behind a code change
+
+
+
+
+
+Summary
+
+This forking workflow lets you propose changes to repositories for
+which you have no write access .
+This is the way that much modern open-source software works.
+You can now contribute to any project you can view.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/collaboration/index.html b/collaboration/index.html
new file mode 100644
index 0000000..a31c441
--- /dev/null
+++ b/collaboration/index.html
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+ Collaborative version control and code review — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/collaboration/same-repository/index.html b/collaboration/same-repository/index.html
new file mode 100644
index 0000000..4c395a0
--- /dev/null
+++ b/collaboration/same-repository/index.html
@@ -0,0 +1,429 @@
+
+
+
+
+
+
+
+
+ Collaborating within the same repository — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Collaborating within the same repository
+In this episode, we will learn how to collaborate within the same repository.
+We will learn how to cross-reference issues and pull requests, how to review
+pull requests, and how to use draft pull requests.
+This exercise will form a good basis for collaboration that is suitable for
+most research groups.
+
+
Note
+
When you read or hear pull request , please think of a change proposal .
+
+
+Exercise
+In this exercise, we will contribute to a repository via a pull request .
+This means that you propose some change, and then it is accepted (or not).
+
+
Exercise preparation
+
+First we need to get access to the exercise repository to which we will
+contribute.
+
+
+Don’t forget to accept the invitation
+
+Check https://github.com/settings/organizations/
+Alternatively check the inbox for the email account you registered with
+GitHub. GitHub emails you an invitation link, but if you don’t receive it
+you can go to your GitHub notifications in the top right corner. The
+maintainer can also “copy invite link” and share it within the group.
+
+
+Watching and unwatching repositories
+
+Now that you are a collaborator, you get notified about new issues and pull
+requests via email.
+If you do not wish this, you can “unwatch” a repository (top of
+the project page).
+However, we recommend watching repositories you are interested
+in. You can learn things from experts just by watching the
+activity that come through a popular project.
+
+
+
+
+Unwatch a repository by clicking “Unwatch” in the repository view,
+then “Participating and @mentions” - this way, you will get
+notifications about your own interactions.
+
+
+
+
+
+
+
Exercise: Collaborating within the same repository (25 min)
+
Technical requirements (from installation instructions):
+
+
What is familiar from the previous workshop day (not repeated here):
+
+
What will be new in this exercise:
+
+If you create the changes locally, you will need to push them to the remote repository.
+Learning what a protected branch is and how to modify a protected branch: using a pull request.
+Cross-referencing issues and pull requests.
+Practice to review a pull request.
+Learn about the value of draft pull requests.
+
+
Exercise tasks :
+
+Start in the exercise
+repository and open an
+issue where you describe the change you want to make. Note down the issue
+number since you will need it later.
+Create a new branch.
+Make a change to the recipe book on the new branch and in the commit
+cross-reference the issue you opened (see the walk-through below
+for how to do that).
+Push your new branch (with the new commit) to the repository you
+are working on.
+Open a pull request towards the main branch.
+Review somebody else’s pull request and give constructive feedback. Merge their pull request.
+Try to create a new branch with some half-finished work and open a draft
+pull request. Verify that the draft pull request cannot be merged since it
+is not meant to be merged yet.
+
+
+
+
+Solution and hints
+
+(1) Opening an issue
+This is done through the GitHub web interface. For example, you could
+give the name of the recipe you want to add (so that others don’t add
+the same one). It is the “Issues” tab.
+
+
+(2) Create a new branch.
+If on GitHub, you can make the branch in the web interface.
+
+
+(3) Make a change adding the recipe
+Add a new file with the recipe in it. Commit the file. In the commit
+message, include the note about the issue number, saying that this
+will close that issue.
+
+Cross-referencing issues and pull requests
+Each issue and each pull request gets a number and you can cross-reference them.
+When you open an issue, note down the issue number (in this case it is #2
):
+
+
+
+
+You can reference this issue number in a commit message or in a pull request, like in this
+commit message:
+ this is the new recipe; fixes #2
+
+
+If you forget to do that in your commit message, you can also reference the issue
+in the pull request description. And instead of fixes
you can also use closes
or resolves
+or fix
or close
or resolve
(case insensitive).
+Here are all the keywords that GitHub recognizes:
+https://help.github.com/en/articles/closing-issues-using-keywords
+Then observe what happens in the issue once your commit gets merged: it will
+automatically close the issue and create a link between the issue and the
+commit. This is very useful for tracking what changes were made in response to
+which issue and to know from when until when precisely the issue was open.
+
+
+
+(4) Push to GitHub as a new branch
+Push the branch to the repository. You should end up with a branch
+visible in the GitHub web view.
+This is only necessary if you created the changes locally. If you created the
+changes directly on GitHub, you can skip this step.
+
+
VS Code Command line
In VS Code, you can “publish the branch” to the remote repository by clicking
+the cloud icon in the bottom left corner of the window:
+
+
+
+
+
If you have a local branch my-branch
and you want to push it to the remote
+and make it visible there, first verify what your remote is:
+
$ git remote --verbose
+
+origin git@github.com:USER/recipe-book.git (fetch)
+origin git@github.com:USER/recipe-book.git (push)
+
+
+
In this case the remote is called origin
and refers to the address
+git@github.com:USER/recipe-book.git. Both can be used
+interchangeably. Make sure it points to the right repository, ideally a
+repository that you can write to.
+
Now that you have a remote, you can push your branch to it:
+
$ git push origin my-branch
+
+
+
This will create a new branch on the remote repository with the same name as
+your local branch.
+
You can also do this:
+
$ git push --set-upstream origin my-branch
+
+
+
This will connect the local branch and the remote branch so that in the future
+you can just type git push
and git pull
without specifying the branch name
+(but this depends on your Git configuration).
+
Troubleshooting
+
If you don’t have a remote yet, you can add it with (adjust ADDRESS
to your repository address):
+
$ git remote add origin ADDRESS
+
+
+
ADDRESS is the part that you copy from here:
+
+
+
+
+
If the remote points to the wrong place, you can change it with:
+
$ git remote set-url origin NEWADDRESS
+
+
+
+
+
+(5) Open a pull request towards the main branch
+This is done through the GitHub web interface.
+
+
+(6) Reviewing pull requests
+You review through the GitHub web interface.
+Checklist for reviewing a pull request:
+
+Be kind, on the other side is a human who has put effort into this.
+Be constructive: if you see a problem, suggest a solution.
+Towards which branch is this directed?
+Is the title descriptive?
+Is the description informative?
+Scroll down to see commits.
+Scroll down to see the changes.
+If you get incredibly many changes, also consider the license or copyright
+and ask where all that code is coming from.
+Again, be kind and constructive.
+Later we will learn how to suggest changes directly in the pull request.
+
+If someone is new, it’s often nice to say something encouraging in the
+comments before merging (even if it’s just “thanks”). If all is good
+and there’s not much else to say, you could merge directly.
+
+
+(7) Draft pull requests
+Try to create a draft pull request:
+
+
+
+
+Verify that the draft pull request cannot be merged until it is marked as ready
+for review:
+
+
+
+
+Draft pull requests can be useful for:
+
+Feedback : You can open a pull request early to get feedback on your work without
+signaling that it is ready to merge.
+Information : They can help communicating to others that a change is coming up and in
+progress.
+
+
+
+What is a protected branch? And how to modify it?
+A protected branch on GitHub or GitLab is a branch that cannot (accidentally)
+deleted or force-pushed to. It is also possible to require that a branch cannot
+be directly pushed to or modified, but that changes must be submitted via a
+pull request.
+To protect a branch in your own repository, go to “Settings” -> “Branches”.
+
+
+Summary
+
+We used all the same pieces that we’ve learned previously.
+But we successfully contributed to a collaborative project !
+The pull request allowed us to contribute without changing directly:
+this is very good when it’s not mainly our project.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/credit/index.html b/credit/index.html
new file mode 100644
index 0000000..9c83915
--- /dev/null
+++ b/credit/index.html
@@ -0,0 +1,161 @@
+
+
+
+
+
+
+
+
+ Credit — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Credit
+This lesson is a mashup of the following sources (all CC-BY):
+
+The lesson uses the following example repository:
+
+The classification task has replaced the “planets” example repository used in
+the original lesson.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dependencies/index.html b/dependencies/index.html
new file mode 100644
index 0000000..5fb8309
--- /dev/null
+++ b/dependencies/index.html
@@ -0,0 +1,617 @@
+
+
+
+
+
+
+
+
+ Reproducible environments and dependencies — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Reproducible environments and dependencies
+
+
Objectives
+
+There are not many codes that have no dependencies.
+How should we deal with dependencies ?
+We will focus on installing and managing dependencies in Python when using packages from PyPI and Conda.
+We will not discuss how to distribute your code as a package.
+
+
+[This episode borrows from https://coderefinery.github.io/reproducible-python/reusable/
+and https://aaltoscicomp.github.io/python-for-scicomp/dependencies/ ]
+Essential XKCD comics:
+
+
+How to avoid: “It works on my machine 🤷”
+Use a standard way to list dependencies in your project:
+
+Python: requirements.txt
or environment.yml
+R: DESCRIPTION
or renv.lock
+Rust: Cargo.lock
+Julia: Project.toml
+C/C++/Fortran: CMakeLists.txt
or Makefile
or spack.yaml
or the module
+system on clusters or containers
+Other languages: …
+
+
+
+Two ecosystems: PyPI (The Python Package Index) and Conda
+
+
PyPI
+
+Installation tool: pip
or uv
or similar
+Traditionally used for Python-only packages or
+for Python interfaces to external libraries. There are also packages
+that have bundled external libraries (such as numpy).
+Pros:
+
+Easy to use
+Package creation is easy
+
+
+Cons:
+
+
+
+
+
+
Conda
+
+Installation tool: conda
or mamba
or similar
+Aims to be a more general package distribution tool
+and it tries to provide not only the Python packages, but also libraries
+and tools needed by the Python packages.
+Pros:
+
+
+Cons:
+
+
+
+
+
+
+Conda ecosystem explained
+
+Anaconda is a distribution of conda packages
+made by Anaconda Inc. When using Anaconda remember to check that your
+situation abides with their licensing terms (see below).
+Anaconda has recently changed its licensing terms , which affects its
+use in a professional setting. This caused uproar among academia
+and Anaconda modified their position in
+this article .
+Main points of the article are:
+
+conda (installation tool) and community channels (e.g. conda-forge)
+are free to use.
+Anaconda repository and Anaconda’s channels in the community repository
+are free for universities and companies with fewer than 200 employees.
+Non-university research institutions and national laboratories need
+licenses.
+Miniconda is free, when it does not download Anaconda’s packages.
+Miniforge is not related to Anaconda, so it is free.
+
+For ease of use on sharing environment files, we recommend using
+Miniforge to create the environments and using conda-forge as the main
+channel that provides software.
+
+Major repositories/channels:
+
+Anaconda Repository
+houses Anaconda’s own proprietary software channels.
+Anaconda’s proprietary channels: main
, r
, msys2
and anaconda
.
+These are sometimes called defaults
.
+conda-forge is the largest open source
+community channel. It has over 28k packages that include open-source
+versions of packages in Anaconda’s channels.
+
+
+
+
+
+
+Best practice: Install dependencies into isolated environments
+
+For each project, create a separate environment .
+Don’t install dependencies globally for all projects. Sooner or later, different projects will have conflicting dependencies.
+Install them from a file which documents them at the same time
+Install dependencies by first recording them in requirements.txt
or
+environment.yml
and install using these files, then you have a trace
+(we will practice this later below).
+
+
+
Keypoints
+
If somebody asks you what dependencies you have in your project, you should be
+able to answer this question with a file .
+
In Python, the two most common ways to do this are:
+
+
You can export (“freeze”) the dependencies from your current environment into these files:
+
# inside a conda environment
+$ conda env export --from-history > environment.yml
+
+# inside a virtual environment
+$ pip freeze > requirements.txt
+
+
+
+
+
+How to communicate the dependencies as part of a report/thesis/publication
+Each notebook or script or project which depends on libraries should come with
+either a requirements.txt
or a environment.yml
, unless you are creating
+and distributing this project as Python package.
+
+Attach a requirements.txt
or a environment.yml
to your thesis.
+Even better: Put requirements.txt
or a environment.yml
in your Git repository along your code.
+Even better: Also binderize your analysis pipeline.
+
+
+
+Containers
+
+A container is like an operating system inside a file .
+“Building a container”: Container definition file (recipe) -> Container image
+This can be used with Apptainer /
+SingularityCE .
+
+Containers offer the following advantages:
+
+Reproducibility : The same software environment can be recreated on
+different computers. They force you to know and document all your dependencies .
+Portability : The same software environment can be run on different computers.
+Isolation : The software environment is isolated from the host system.
+“Time travel ”:
+
+
+
+
+
+How to install dependencies into environments
+Now we understand a bit better why and how we installed dependencies
+for this course in the Software install instructions .
+We have used Miniforge and the long command we have used was:
+$ mamba env create -n course -f https://raw.githubusercontent.com/coderefinery/reproducible-python-ml/main/software/environment.yml
+
+
+This command did two things:
+
+Create a new environment with name “course” (specified by -n
).
+Installed all dependencies listed in the environment.yml
file (specified by
+-f
), which we fetched directly from the web.
+Here
+you can browse it.
+
+For your own projects:
+
+Start by writing an environment.yml
of requirements.txt
file. They look like this:
+
+
+
environment.yml requirements.txt
name : course
+channels :
+ - conda-forge
+ - bioconda
+dependencies :
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - jupyterlab
+ - pytest
+ - scalene
+ - flit
+ - ruff
+ - icecream
+ - snakemake-minimal
+ - myst-parser
+ - sphinx
+ - sphinx-rtd-theme
+ - sphinx-autoapi
+ - sphinx-autobuild
+ - black
+ - isort
+ - pip
+ - pip :
+ - jupyterlab-code-formatter
+
+
+
click
+numpy
+pandas
+scipy
+altair
+vl - convert - python
+jupyterlab
+pytest
+scalene
+flit
+ruff
+icecream
+myst - parser
+sphinx
+sphinx - rtd - theme
+sphinx - autoapi
+sphinx - autobuild
+black
+isort
+jupyterlab - code - formatter
+
+
+
+
+Then set up an isolated environment and install the dependencies from the file into it:
+
+
+
Miniforge Pixi Virtual environment uv
+Create a new environment with name “myenv” from environment.yml
:
+$ conda env create -n myenv -f environment.yml
+
+
+Or equivalently:
+$ mamba env create -n myenv -f environment.yml
+
+
+
+Activate the environment:
+
+
+Run your code inside the activated virtual environment.
+
+
+
+
+Create a virtual environment by running (the second argument is the name
+of the environment and you can change it):
+
+
+Activate the virtual environment (how precisely depends on your operating
+system and shell).
+Install the dependencies:
+$ python -m pip install -r requirements.txt
+
+
+
+Run your code inside the activated virtual environment.
+
+
+
+
+Create a virtual environment by running (the second argument is the name
+of the environment and you can change it):
+
+
+Activate the virtual environment (how precisely depends on your operating
+system and shell).
+Install the dependencies:
+$ uv pip sync requirements.txt
+
+
+
+Run your code inside the virtual environment.
+$ uv run python example.py
+
+
+
+
+
+
+
+Updating environments
+What if you forgot a dependency? Or during the development of your project
+you realize that you need a new dependency? Or you don’t need some dependency anymore?
+
+Modify the environment.yml
or requirements.txt
file.
+Either remove your environment and create a new one, or update the existing one:
+
+
+
Miniforge Pixi Virtual environment uv
+
+
+Pinning package versions
+Let us look at the
+environment.yml
+which we used to set up the environment for this course.
+Dependencies are listed without version numbers. Should we pin the
+versions ?
+
+Both pip
and conda
ecosystems and all the tools that we have
+mentioned support pinning versions.
+It is possible to define a range of versions instead of precise versions.
+While your project is still in progress, I often use latest versions and do not pin them.
+When publishing the script or notebook, it is a good idea to pin the versions
+to ensure that the code can be run in the future.
+Remember that at some point in time you will face a situation where
+newer versions of the dependencies are no longer compatible with your
+software. At this point you’ll have to update your software to use the newer
+versions or to lock it into a place in time.
+
+
+
+Managing dependencies on a supercomputer
+
+Additional challenges:
+
+Storage quotas: Do not install dependencies in your home directory . A conda environment can easily contain 100k files.
+Network file systems struggle with many small files. Conda environments often contain many small files.
+
+
+Possible solutions:
+
+Try Pixi (modern take on managing Conda environments) and
+uv (modern take on managing virtual
+environments). Blog post: Using Pixi and uv on a supercomputer
+Install your environment on the fly into a scratch directory on local disk (not the network file system).
+Install your environment on the fly into a RAM disk/drive.
+Containerize your environment into a container image.
+
+
+
+
+
+
Keypoints
+
+Being able to communicate your dependencies is not only nice for others, but
+also for your future self or the next PhD student or post-doc.
+If you ask somebody to help you with your code, they will ask you for the
+dependencies.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/documentation/index.html b/documentation/index.html
new file mode 100644
index 0000000..cb1f32c
--- /dev/null
+++ b/documentation/index.html
@@ -0,0 +1,560 @@
+
+
+
+
+
+
+
+
+ Where to start with documentation — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Where to start with documentation
+
+
Objectives
+
+Discuss what makes good documentation.
+Improve the README of your project or our example project.
+Explore Sphinx which is a popular tool to build documentation websites.
+Learn how to leverage GitHub Actions and GitHub Pages to build and deploy documentation.
+
+
+
+
+Why? 💗✉️ to your future self
+
+
+
+In-code documentation
+Not very useful (more commentary than comment):
+# now we check if temperature is below -50
+if temperature < - 50 :
+ print ( "ERROR: temperature is too low" )
+
+
+More useful (explaining why ):
+# we regard temperatures below -50 degrees as measurement errors
+if temperature < - 50 :
+ print ( "ERROR: temperature is too low" )
+
+
+Keeping zombie code “just in case” (rather use version control):
+# do not run this code!
+# if temperature > 0:
+# print("It is warm")
+
+
+Emulating version control:
+# John Doe: threshold changed from 0 to 15 on August 5, 2013
+if temperature > 15 :
+ print ( "It is warm" )
+
+
+
+
+Many languages allow “docstrings”
+Example (Python):
+def kelvin_to_celsius ( temp_k : float ) -> float :
+ """
+ Converts temperature in Kelvin to Celsius.
+
+ Parameters
+ ----------
+ temp_k : float
+ temperature in Kelvin
+
+ Returns
+ -------
+ temp_c : float
+ temperature in Celsius
+ """
+ assert temp_k >= 0.0 , "ERROR: negative T_K"
+
+ temp_c = temp_k - 273.15
+
+ return temp_c
+
+
+
+
Keypoints
+
+Documentation which is only in the source code is not enough.
+Often a README is enough.
+Documentation needs to be kept
+in the same Git repository as the code since we want it to evolve with
+the code.
+
+
+
+
+Often a README is enough - checklist
+
+Purpose
+Requirements
+Installation instructions
+Copy-paste-able example to get started
+Tutorials covering key functionality
+Reference documentation (e.g. API) covering all functionality
+Authors and recommended citation
+License
+Contribution guide
+
+See also the
+JOSS review checklist .
+
+
+Diátaxis
+Diátaxis is a systematic approach to technical documentation authoring.
+
+
+
+What if you need more than a README?
+
+
+
+Exercise: Set up a Sphinx documentation
+
+
Preparation
+
In this episode we will use the following 5 packages which we installed
+previously as part of the Software install instructions :
+
myst - parser
+sphinx
+sphinx - rtd - theme
+sphinx - autoapi
+sphinx - autobuild
+
+
+
Which repository to use? You have 3 options:
+
+Clone your fork of the example repository.
+If you don’t have that, you can clone the exercise repository itself.
+You can try this with your own project and the project does not have to
+be a Python project.
+
+
+There are at least two ways to get started with Sphinx:
+
+Use sphinx-quickstart
to create a new Sphinx project.
+This is what we will do instead : Create three files (doc/conf.py
, doc/index.md
, and doc/about.md
)
+as starting point and improve from there.
+
+
+
Exercise: Set up a Sphinx documentation
+
+Create the following three files in your project:
+ your-project/
+├── doc/
+│ ├── conf.py
+│ ├── index.md
+│ └── about.md
+└── ...
+
+
+This is conf.py
:
+project = "your-project"
+copyright = "2025, Authors"
+author = "Authors"
+release = "0.1"
+
+exclude_patterns = [ "_build" , "Thumbs.db" , ".DS_Store" ]
+
+extensions = [
+ "myst_parser" , # in order to use markdown
+]
+
+myst_enable_extensions = [
+ "colon_fence" , # ::: can be used instead of ``` for better rendering
+]
+
+html_theme = "sphinx_rtd_theme"
+
+
+This is index.md
(feel free to change the example text):
+# Our code documentation
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
+incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
+nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
+culpa qui officia deserunt mollit anim id est laborum.
+
+:::{toctree}
+:maxdepth: 2
+:caption: Some caption
+
+about.md
+:::
+
+
+This is about.md
(feel free to adjust):
+# About this code
+
+Work in progress ...
+
+
+
+Run sphinx-build
to build the HTML documentation:
+$ sphinx-build doc _build
+
+... lots of output ...
+The HTML pages are in _build.
+
+
+
+Try to open _build/index.html
in your browser.
+Experiment with adding more content, images, equations, code blocks, …
+
+
+
+
+There is a lot more you can do:
+
+This is useful if you want to check the integrity of all internal and external links:
+$ sphinx-build doc -W -b linkcheck _build
+
+
+
+sphinx-autobuild
+provides a local web server that will automatically refresh your view
+every time you save a file - which makes writing with live-preview much easier.
+
+
+
+Demo: Building documentation with GitHub Actions
+
+First we need to extend the environment.yml
file to include the necessary packages:
+name : classification-task
+channels :
+ - conda-forge
+dependencies :
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - myst-parser
+ - sphinx
+ - sphinx-rtd-theme
+ - sphinx-autoapi
+
+
+Then we add a GitHub Actions workflow .github/workflow/sphinx.yml
to build the documentation:
+name : Build documentation
+
+on :
+ push :
+ branches : [ main ]
+ pull_request :
+ branches : [ main ]
+
+permissions :
+ contents : write
+
+jobs :
+ docs :
+ runs-on : ubuntu-24.04
+
+ steps :
+ - name : Checkout
+ uses : actions/checkout@v4
+
+ - uses : mamba-org/setup-micromamba@v1
+ with :
+ micromamba-version : '2.0.5-0' # any version from https://github.com/mamba-org/micromamba-releases
+ environment-file : environment.yml
+ init-shell : bash
+ cache-environment : true
+ post-cleanup : 'all'
+ generate-run-shell : false
+
+ - name : Sphinx build
+ run : |
+ sphinx-build doc _build
+ shell : bash -el {0}
+
+ - name : Deploy to GitHub Pages
+ uses : peaceiris/actions-gh-pages@v4
+ if : ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
+ with :
+ publish_branch : gh-pages
+ github_token : ${{ secrets.GITHUB_TOKEN }}
+ publish_dir : _build/
+ force_orphan : true
+
+
+Now:
+
+Add these two changes to the GitHub repository.
+Go to “Settings” -> “Pages” -> “Branch” -> gh-pages
-> “Save”.
+Look at “Actions” tab and observe the workflow running and hopefully
+deploying the website.
+Finally visit the generated site. You can find it by clicking the About wheel
+icon on top right of your repository. There, select “Use your GitHub Pages
+website”.
+This is how we build almost all of our lesson websites ,
+including this one!
+Another popular place to deploy Sphinx documentation is ReadTheDocs .
+
+
+
+Optional: How to auto-generate API documentation in Python
+Add three tiny modifications (highlighted) to doc/conf.py
to auto-generate API documentation
+(this requires the sphinx-autoapi
package):
+project = "your-project"
+copyright = "2025, Authors"
+author = "Authors"
+release = "0.1"
+
+exclude_patterns = [ "_build" , "Thumbs.db" , ".DS_Store" ]
+
+extensions = [
+ "myst_parser" , # in order to use markdown
+ "autoapi.extension" , # in order to use markdown
+ ]
+
+# search this directory for Python files
+autoapi_dirs = [ ".." ]
+
+# ignore this file when generating API documentation
+autoapi_ignore = [ "*/conf.py" ]
+
+myst_enable_extensions = [
+ "colon_fence" , # ::: can be used instead of ``` for better rendering
+]
+
+html_theme = "sphinx_rtd_theme"
+
+
+Then rebuild the documentation (or push the changes and let GitHub rebuild it)
+and you should see a new section “API Reference”.
+
+
+Possibilities to host Sphinx documentation
+
+
+
+Confused about reStructuredText vs. Markdown vs. MyST?
+
+At the beginning there was reStructuredText and Sphinx was built for reStructuredText.
+Independently, Markdown was invented and evolved into a couple of flavors.
+Markdown became more and more popular but was limited compared to reStructuredText.
+Later, MyST
+was invented to be able to write
+something that looks like Markdown but in addition can do everything that
+reStructuredText can do with extra directives.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/index.html b/example/index.html
new file mode 100644
index 0000000..ada6834
--- /dev/null
+++ b/example/index.html
@@ -0,0 +1,251 @@
+
+
+
+
+
+
+
+
+ Example project: 2D classification task using a nearest-neighbor predictor — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+ Example project: 2D classification task using a nearest-neighbor predictor
+
+ Edit on GitHub
+
+
+
+
+
+
+
+
+Example project: 2D classification task using a nearest-neighbor predictor
+The example code
+that we will study is a relatively simple nearest-neighbor predictor written in
+Python. It is not important or expected that we understand the code in detail.
+The code will produce something like this:
+
+
+
+
+The bottom row shows the training data (two labels) and the top row shows the
+test data and whether the nearest-neighbor predictor classified their labels
+correctly.
+
+
+The big picture of the code is as follows:
+
+We can choose the number of samples (the example above has 50 samples).
+The code will generate samples with two labels (0 and 1) in a 2D space.
+One of the labels has a normal distribution and a circular distribution with
+some minimum and maximum radius.
+The second label only has a circular distribution with a different radius.
+Then we try to predict whether the test samples belong to label 0 or 1 based
+on the nearest neighbors in the training data. The number of neighbors can
+be adjusted and the code will take label of the majority of the neighbors.
+
+
+Example run
+
+
Instructor note
+
The instructor demonstrates running the code on their computer.
+
+The code is written to accept command-line arguments to specify the number
+of samples and file names. Later we will discuss advantages of this approach.
+Let us try to get the help text:
+$ python generate_data.py --help
+
+Usage: generate_data.py [OPTIONS]
+
+ Program that generates a set of training and test samples for a non-linear
+ classification task.
+
+Options:
+ --num-samples INTEGER Number of samples for each class. [required]
+ --training-data TEXT Training data is written to this file. [required]
+ --test-data TEXT Test data is written to this file. [required]
+ --help Show this message and exit.
+
+
+We first generate the training and test data:
+$ python generate_data.py --num-samples 50 --training-data train.csv --test-data test.csv
+
+Generated 50 training samples (train.csv) and test samples (test.csv).
+
+
+In a second step we generate predictions for the test data:
+$ python generate_predictions.py --num-neighbors 7 --training-data train.csv --test-data test.csv --predictions predictions.csv
+
+Predictions saved to predictions.csv
+
+
+Finally, we can plot the results:
+$ python plot_results.py --training-data train.csv --predictions predictions.csv --output-chart chart.svg
+
+Accuracy: 0.94
+Saved chart to chart.svg
+
+
+
+
+Discussion and goals
+
+
+
+
We will not focus on …
+
+… how the code works internally in detail.
+… whether this is the most efficient algorithm.
+… whether the code is numerically stable.
+… how to code scales with system size.
+… whether it is portable to other operating systems (we will discuss this later).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/genindex/index.html b/genindex/index.html
new file mode 100644
index 0000000..71173c8
--- /dev/null
+++ b/genindex/index.html
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+ Index — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/good-practices/index.html b/good-practices/index.html
new file mode 100644
index 0000000..b11cd5a
--- /dev/null
+++ b/good-practices/index.html
@@ -0,0 +1,717 @@
+
+
+
+
+
+
+
+
+ Tools and useful practices — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..d8f3f08
--- /dev/null
+++ b/index.html
@@ -0,0 +1,294 @@
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example) — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+ Edit on GitHub
+
+
+
+
+
+
+
+
+Reproducible research software development using Python (ML example)
+
+Big-picture goal
+This is a hands-on course on research software engineering . In this
+workshop we assume that most workshop participants use Python in their work or
+are leading a group which uses Python. Therefore, some of the examples will use
+Python as the example language.
+We will work with an example project (Example project: 2D classification task using a nearest-neighbor predictor )
+and go through all important steps of a typical
+software project. Once we have seen the building blocks, we will try to apply
+them to own projects .
+
+
Preparation
+
+Get a GitHub account following these instructions .
+You will need a text editor . If you don’t have a favorite one, we recommend
+VS Code .
+If you prefer to work in the terminal and not in VS Code, set up these two (skip this if you use VS Code):
+
+
+Follow Software install instructions (but we will do this together at the beginning of the workshop).
+
+
+
+
+Schedule
+
+
+
+Wednesday
+
+09:00-10:00 - Automated testing
+10:15-11:30 - Modular code development
+
+
+11:30-12:15 - Lunch break
+12:15-14:00 - How to release and publish your code
+
+
+14:15-15:00 - Debriefing and Q&A
+
+Participants work on their projects
+Together we study actual codes that participants wrote or work on
+Constructively we discuss possible improvements
+Give individual feedback on code projects
+
+
+
+
+
+Thursday
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/installation/index.html b/installation/index.html
new file mode 100644
index 0000000..5b4bcaf
--- /dev/null
+++ b/installation/index.html
@@ -0,0 +1,423 @@
+
+
+
+
+
+
+
+
+ Software install instructions — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Software install instructions
+[this page is adapted from https://aaltoscicomp.github.io/python-for-scicomp/installation/ ]
+
+Choosing an installation method
+For this course we will install an isolated environment
+with following dependencies:
+
+
environment.yml requirements.txt
name : course
+channels :
+ - conda-forge
+ - bioconda
+dependencies :
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - jupyterlab
+ - pytest
+ - scalene
+ - flit
+ - ruff
+ - icecream
+ - snakemake-minimal
+ - myst-parser
+ - sphinx
+ - sphinx-rtd-theme
+ - sphinx-autoapi
+ - sphinx-autobuild
+ - black
+ - isort
+ - pip
+ - pip :
+ - jupyterlab-code-formatter
+
+
+
click
+numpy
+pandas
+scipy
+altair
+vl - convert - python
+jupyterlab
+pytest
+scalene
+flit
+ruff
+icecream
+myst - parser
+sphinx
+sphinx - rtd - theme
+sphinx - autoapi
+sphinx - autobuild
+black
+isort
+jupyterlab - code - formatter
+
+
+
+
+If you are new to Python or unsure how to create isolated environments in
+Python from files above, please follow the
+instructions below.
+
+
There are many choices and we try to suggest a good compromise
+
There are very many ways to install Python and packages with pros and cons and
+in addition there are several operating systems with their own quirks. This
+can be a huge challenge for beginners to navigate. It can also difficult for
+instructors to give recommendations for something which will work everywhere
+and which everybody will like.
+
Below we will recommend Miniforge since it is free, open source, general,
+available on all operating systems, and provides a good basis for reproducible
+environments. However, it does not provide a graphical user interface during
+installation. This means that every time we want to start a JupyterLab session,
+we will have to go through the command line.
+
+
+
Python, conda, anaconda, miniforge, etc?
+
Unfortunately there are many options and a lot of jargon.
+Here is a crash course:
+
+Python is a programming language very commonly used in
+science, it’s the topic of this course.
+Conda is a package manager: it allows distributing and
+installing packages, and is designed for complex scientific
+code.
+Mamba is a re-implementation of Conda to be much faster with
+resolving dependencies and installing things.
+An environment is a self-contained collections of packages
+which can be installed separately from others. They are used so
+each project can install what it needs without affecting others.
+Anaconda is a commercial distribution of Python+Conda+many
+packages that all work together. It used to be freely usable for
+research, but since ~2023-2024 it’s more limited. Thus, we don’t
+recommend it (even though it has a nice graphical user interface).
+conda-forge is another channel of distributing packages that
+is maintained by the community, and thus can be used by anyone.
+(Anaconda’s parent company also hosts conda-forge packages)
+Miniforge is a distribution of conda pre-configured for
+conda-forge. It operates via the command line.
+Miniconda is a distribution of conda pre-configured to use
+the Anaconda channels.
+
+
We will gain a better background and overview in the section
+Reproducible environments and dependencies .
+
+
+
+Installing Python via Miniforge
+Follow the instructions on the miniforge web page . This installs
+the base, and from here other packages can be installed.
+Unsure what to download and what to do with it?
+
+
Windows MacOS Linux
You want to download and run Miniforge3-Windows-x86_64.exe
.
+
You probably want to download the Miniforge3-MacOSX-arm64.sh
file (unless
+you don’t have an Arm processor) and then run it in the terminal with:
+
$ bash Miniforge3-MacOSX-arm64.sh
+
+
+
You probably want to download the Miniforge3-Linux-x86_64.sh
file (unless
+you don’t have an x86_64 processor) and then run it in the terminal with:
+
$ bash Miniforge3-Linux-x86_64.sh
+
+
+
+
+
+Installing and activating the software environment
+First we will start Python in a way that activates conda/mamba. Then we will
+install the software environment from this environment.yml
+file .
+An environment is a self-contained set of extra libraries - different
+projects can use different environments to not interfere with each other. This
+environment will have all of the software needed for this particular course.
+We will call the environment course
.
+
+
Windows Linux / MacOS
Use the “Miniforge Prompt” to start Miniforge. This
+will set up everything so that conda
and mamba
are
+available.
+Then type
+(without the $
):
+
$ mamba env create -n course -f https://raw.githubusercontent.com/coderefinery/reproducible-python-ml/main/software/environment.yml
+
+
+
Each time you start a new command line terminal,
+you can activate Miniforge by running
+(without the $
):
+
$ source ~/miniforge3/bin/activate
+
+
+
This is needed so that
+Miniforge is usable wherever you need, but doesn’t affect any
+other software on your computer (this is not needed if you
+choose “Do you wish to update your shell profile to
+automatically initialize conda?”, but then it will always be
+active).
+
In the second step, we will install the software environment:
+
$ mamba env create -n course -f https://raw.githubusercontent.com/coderefinery/reproducible-python-ml/main/software/environment.yml
+
+
+
+
+
+Starting JupyterLab
+Every time we want to start a JupyterLab session,
+we will have to go through the command line and first
+activate the course
environment.
+
+
Windows Linux / MacOS
Start the Miniforge Prompt. Then type
+(without the $
):
+
$ conda activate course
+$ jupyter-lab
+
+
+
Start the terminal and in the terminal, type
+(without the $
):
+
$ source ~/miniforge3/bin/activate
+$ conda activate course
+$ jupyter-lab
+
+
+
+
+
+Removing the software environment
+
+
Windows Linux / MacOS
In the Miniforge Prompt, type
+(without the $
):
+
$ conda env list
+$ conda env remove --name course
+$ conda env list
+
+
+
In the terminal, type
+(without the $
):
+
$ source ~/miniforge3/bin/activate
+$ conda env list
+$ conda env remove --name course
+$ conda env list
+
+
+
+
+
+How to verify your installation
+Start JupyterLab (as described above). It will hopefully open up your browser
+and look like this:
+
+
+
+
+JupyterLab opened in the browser. Click on the Python 3 tile.
+
+
+Once you clicked the Python 3 tile it should look like this:
+
+
+
+
+Python 3 notebook started.
+
+
+Into that blue “cell” please type the following:
+import altair
+import pandas
+
+print ( "all good - ready for the course" )
+
+
+
+
+
+
+Please copy these lines and click on the “play”/”run” icon.
+
+
+This is how it should look:
+
+
+
+
+Screenshot after successful import.
+
+
+If this worked, you are all set and can close JupyterLab (no need to save these
+changes).
+This is how it should not look:
+
+
+
+
+Error: required packages could not be found.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lesson.pdf b/lesson.pdf
new file mode 100644
index 0000000..6ce3b3a
Binary files /dev/null and b/lesson.pdf differ
diff --git a/notebooks/sharing/index.html b/notebooks/sharing/index.html
new file mode 100644
index 0000000..aac7188
--- /dev/null
+++ b/notebooks/sharing/index.html
@@ -0,0 +1,246 @@
+
+
+
+
+
+
+
+
+ Sharing notebooks — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Sharing notebooks
+
+[this lesson is adapted after https://coderefinery.github.io/jupyter/sharing/ ]
+
+Document dependencies
+
+If you import libraries into your notebook, note down their versions.
+Document the dependencies as discussed in section Reproducible environments and dependencies .
+Place either environment.yml
or requirements.txt
in the same folder as
+the notebook(s).
+If you publish the notebook as part of a publication, it is probably a good
+idea to pin the versions of the libraries you use.
+This is not only useful for people who will try to rerun this in future, it
+is also understood by some tools (e.g. Binder ) which we
+will see later.
+
+
+
+Different ways to share a notebook
+We need to learn how to share notebooks. At the minimum we need
+to share them with our future selves (backup and reproducibility).
+
+The following platforms can be used free of charge but have paid subscriptions for
+faster access to cloud resources:
+
+
+
+Sharing dynamic notebooks using Binder
+
+
Exercise/demo: Making your notebooks reproducible by anyone (15 min)
+
Instructor demonstrates this:
+
+First we look at the statically rendered version of the example
+notebook
+on GitHub and also nbviewer .
+Visit https://mybinder.org :
+
+
+
+
+Check that your notebook repository now has a “launch binder”
+badge in your README.md
file on GitHub.
+Try clicking the button and see how your repository is launched
+on Binder (can take a minute or two). Your notebooks can now be
+explored and executed in the cloud.
+Enjoy being fully reproducible!
+
+
+
+
+How to get a digital object identifier (DOI)
+
+Zenodo is a great service to get a
+DOI for a notebook
+(but first practice with the Zenodo sandbox ).
+Binder can also run notebooks from Zenodo.
+In the supporting information of your paper you can refer to its DOI.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/notebooks/tooling/index.html b/notebooks/tooling/index.html
new file mode 100644
index 0000000..a1f8044
--- /dev/null
+++ b/notebooks/tooling/index.html
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+ Other useful tooling for notebooks — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/objects.inv b/objects.inv
new file mode 100644
index 0000000..0ed9fab
Binary files /dev/null and b/objects.inv differ
diff --git a/packaging/index.html b/packaging/index.html
new file mode 100644
index 0000000..fe538f1
--- /dev/null
+++ b/packaging/index.html
@@ -0,0 +1,334 @@
+
+
+
+
+
+
+
+
+ Creating a Python package and deploying it to PyPI — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+ Creating a Python package and deploying it to PyPI
+
+ Edit on GitHub
+
+
+
+
+
+
+
+
+Creating a Python package and deploying it to PyPI
+
+
Objectives
+
In this episode we will create a pip-installable Python package and learn how
+to deploy it to PyPI. As example, we can use one of the Python scripts from our
+example repository.
+
+
+Creating a Python package with the help of Flit
+There are unfortunately many ways to package a Python project:
+
+setuptools
is the most common way to package a Python project. It is very
+powerful and flexible, but also can get complex.
+flit
is a simpler alternative to setuptools
. It is less versatile, but
+also easier to use.
+poetry
is a modern packaging tool which is more versatile than flit
and
+also easier to use than setuptools
.
+twine
is another tool to upload packages to PyPI.
+…
+
+
+
This will be a demo
+
+We will try to package the code together on the big screen.
+We will share the result on GitHub so that you can retrace the steps.
+In this demo, we will use Flit to package the code. From Why use Flit? :
+
+Make the easy things easy and the hard things possible is an old motto from
+the Perl community. Flit is entirely focused on the easy things part of that,
+and leaves the hard things up to other tools.
+
+
+
+
+
+
+
+Step 2: Testing an install from GitHub
+If a local install worked, push the pyproject.toml
to GitHub and try to install the package from GitHub.
+In a requirements.txt
file, you can specify the GitHub repository and the
+branch (adapt the names):
+git + https : // github . com / ORGANIZATION / REPOSITORY . git @main
+
+
+A corresponding envionment.yml
file for conda would look like this (adapt the
+names):
+name : experiment
+channels :
+ - conda-forge
+dependencies :
+ - python <= 3.12
+ - pip
+ - pip :
+ - git+https://github.com/ORGANIZATION/REPOSITORY.git@main
+
+
+Does it install and run? If yes, move on to the next step (test-PyPI and later
+PyPI).
+
+
+Step 3: Deploy the package to test-PyPI using GitHub Actions
+Our final step is to create a GitHub Actions workflow which will run when we
+create a new release.
+
+
Danger
+
I recommend to first practice this on the test-PyPI before deploying to the
+real PyPI.
+
+Here is the workflow (.github/workflows/package.yml
):
+name : Package
+
+on :
+ release :
+ types : [ created ]
+
+jobs :
+ build :
+ permissions : write-all
+ runs-on : ubuntu-24.04
+
+ steps :
+ - name : Switch branch
+ uses : actions/checkout@v4
+ - name : Set up Python
+ uses : actions/setup-python@v5
+ with :
+ python-version : "3.12"
+ - name : Install Flit
+ run : |
+ pip install flit
+ - name : Flit publish
+ run :
+ flit publish
+ env :
+ FLIT_USERNAME : __token__
+ FLIT_PASSWORD : ${{ secrets.PYPI_TOKEN }}
+ # uncomment the following line if you are using test.pypi.org:
+# FLIT_INDEX_URL: https://test.pypi.org/legacy/
+ # this is how you can test the installation from test.pypi.org:
+# pip install --index-url https://test.pypi.org/simple/ package_name
+
+
+About the FLIT_PASSWORD: ${{ secrets.PYPI_TOKEN }}
:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/profiling/index.html b/profiling/index.html
new file mode 100644
index 0000000..63cafec
--- /dev/null
+++ b/profiling/index.html
@@ -0,0 +1,432 @@
+
+
+
+
+
+
+
+
+ Profiling — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Profiling
+
+
Objectives
+
+Understand when improving code performance is worth the time and effort.
+Knowing how to find performance bottlenecks in Python code.
+Try Scalene as one of many tools
+to profile Python code.
+
+
+[This page is adapted after https://aaltoscicomp.github.io/python-for-scicomp/profiling/ ]
+
+Should we even optimize the code?
+Classic quote to keep in mind: “Premature optimization is the root of all evil.” [Donald Knuth]
+
+
Discussion
+
It is important to ask ourselves whether it is worth it.
+
+
Depends. What does it depend on?
+
+
+
+Measure instead of guessing
+Before doing code surgery to optimize the run time or lower the memory usage,
+we should measure where the bottlenecks are. This is called profiling .
+Analogy: Medical doctors don’t start surgery based on guessing. They first measure
+(X-ray, MRI, …) to know precisely where the problem is.
+Not only programming beginners can otherwise guess wrong, but also experienced
+programmers can be surprised by the results of profiling.
+
+
+
+
+Tracing profilers vs. sampling profilers
+Tracing profilers record every function call and event in the program,
+logging the exact sequence and duration of events.
+
+Sampling profilers periodically samples the program’s state (where it is
+and how much memory is used), providing a statistical view of where time is
+spent.
+
+Pros:
+
+
+Cons:
+
+Less precise, potentially missing infrequent or short calls.
+Provides an approximation rather than exact timing.
+
+
+
+
+
Analogy: Imagine we want to optimize the London Underground (subway) system
+
We wish to detect bottlenecks in the system to improve the service and for this we have
+asked few passengers to help us by tracking their journey.
+
+Tracing : We follow every train and passenger, recording every stop
+and delay. When passengers enter and exit the train, we record the exact time
+and location.
+Sampling : Every 5 minutes the phone notifies the passenger to note
+down their current location. We then use this information to estimate
+the most crowded stations and trains.
+
+
+
+
+Choosing the right system size
+Sometimes we can configure the system size (for instance the time step in a simulation
+or the number of time steps or the matrix dimensions) to make the program finish sooner.
+For profiling, we should choose a system size that is representative of the real-world
+use case. If we profile a program with a small input size, we might not see the same
+bottlenecks as when running the program with a larger input size.
+Often, when we scale up the system size, or scale the number of processors, new bottlenecks
+might appear which we didn’t see before. This brings us back to: “measure instead of guessing”.
+
+
+Exercises
+
+
Exercise: Practicing profiling
+
In this exercise we will use the Scalene profiler to find out where most of the time is spent
+and most of the memory is used in a given code example.
+
Please try to go through the exercise in the following steps:
+
+Make sure scalene
is installed in your environment (if you have followed
+this course from the start and installed the recommended software
+environment, then it is).
+Download Leo Tolstoy’s “War and Peace” from the following link (the text is
+provided by Project Gutenberg ):
+https://www.gutenberg.org/cache/epub/2600/pg2600.txt
+(right-click and “save as” to download the file and save it as “book.txt” ).
+Before you run the profiler, try to predict in which function the code
+(the example code is below)
+will spend most of the time and in which function it will use most of the
+memory.
+Save the example code as example.py
and
+run the scalene
profiler on the following code example and browse the
+generated HTML report to find out where most of the time is spent and where
+most of the memory is used:
+
+Alternatively you can do this (and then open the generated file in a browser):
+$ scalene example.py --html > profile.html
+
+
+You can find an example of the generated HTML report in the solution below.
+
+Does the result match your prediction? Can you explain the results?
+
+
Example code (example.py
):
+
"""
+The code below reads a text file and counts the number of unique words in it
+(case-insensitive).
+"""
+import re
+
+
+def count_unique_words1 ( file_path : str ) -> int :
+ with open ( file_path , "r" , encoding = "utf-8" ) as file :
+ text = file . read ()
+ words = re . findall ( r "\b\w+\b" , text . lower ())
+ return len ( set ( words ))
+
+
+def count_unique_words2 ( file_path : str ) -> int :
+ unique_words = []
+ with open ( file_path , "r" , encoding = "utf-8" ) as file :
+ for line in file :
+ words = re . findall ( r "\b\w+\b" , line . lower ())
+ for word in words :
+ if word not in unique_words :
+ unique_words . append ( word )
+ return len ( unique_words )
+
+
+def count_unique_words3 ( file_path : str ) -> int :
+ unique_words = set ()
+ with open ( file_path , "r" , encoding = "utf-8" ) as file :
+ for line in file :
+ words = re . findall ( r "\b\w+\b" , line . lower ())
+ for word in words :
+ unique_words . add ( word )
+ return len ( unique_words )
+
+
+def main ():
+ # book.txt is downloaded from https://www.gutenberg.org/cache/epub/2600/pg2600.txt
+ _result = count_unique_words1 ( "book.txt" )
+ _result = count_unique_words2 ( "book.txt" )
+ _result = count_unique_words3 ( "book.txt" )
+
+
+if __name__ == "__main__" :
+ main ()
+
+
+
+
Solution
+
+
+
+
+Result of the profiling run for the above code example. You can click on the image to make it larger.
+
+
+
Results:
+
+
Explanation:
+
+The count_unique_words2
function is the slowest because it uses a list
+to store unique words and checks if a word is already in the list before
+adding it.
+Checking whether a list contains an element might require traversing the
+whole list, which is an O(n) operation. As the list grows in size,
+the lookup time increases with the size of the list.
+The count_unique_words1
and count_unique_words3
functions are faster
+because they use a set to store unique words.
+Checking whether a set contains an element is an O(1) operation.
+The count_unique_words1
function uses the most memory because it creates
+a list of all words in the text file and then creates a set from that
+list.
+The count_unique_words3
function uses less memory because it traverses
+the text file line by line instead of reading the whole file into memory.
+
+
What we can learn from this exercise:
+
+When processing large files, it can be good to read them line by line
+or in batches
+instead of reading the whole file into memory.
+It is good to get an overview over standard data structures and their
+advantages and disadvantages (e.g. adding an element to a list is fast but checking whether
+it already contains the element can be slow).
+
+
+
+
+
+Additional resources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/publishing/index.html b/publishing/index.html
new file mode 100644
index 0000000..59c0acd
--- /dev/null
+++ b/publishing/index.html
@@ -0,0 +1,305 @@
+
+
+
+
+
+
+
+
+ How to publish your code — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+How to publish your code
+
+
+Is putting software on GitHub/GitLab/… publishing?
+
+
+
+
+FAIR principles. (c) Scriberia for The Turing Way , CC-BY.
+
+
+Is it enough to make the code public for the code to remain findable and accessible ?
+
+No. Because nothing prevents me from deleting my GitHub repository or
+rewriting the Git history and we have no guarantee that GitHub will still be around in 10 years.
+Make your code citable and persistent :
+Get a persistent identifier (PID) such as DOI in addition to sharing the
+code publicly, by using services like Zenodo or
+similar services.
+
+
+
+How to make your software citable
+
+
Discussion: Explain how you currently cite software
+
+Do you cite software that you use? How?
+If I wanted to cite your code/scripts, what would I need to do?
+
+
+Checklist for making a release of your software citable :
+
+Assigned an appropriate license
+Described the software using an appropriate metadata format
+Clear version number
+Authors credited
+Procured a persistent identifier
+Added a recommended citation to the software documentation
+
+This checklist is adapted from: N. P. Chue Hong, A. Allen, A. Gonzalez-Beltran,
+et al., Software Citation Checklist for Developers (Version 0.9.0). Zenodo.
+2019b. (DOI )
+Our practical recommendations :
+
+This is an example of a simple CITATION.cff
file:
+cff-version : 1.2.0
+message : "If you use this software, please cite it as below."
+authors :
+ - family-names : Doe
+ given-names : Jane
+ orcid : https://orcid.org/1234-5678-9101-1121
+title : "My Research Software"
+version : 2.0.4
+doi : 10.5281/zenodo.1234
+date-released : 2021-08-11
+
+
+More about CITATION.cff
files:
+
+
+
+
+How to cite software
+
+
Great resources
+
+A. M. Smith, D. S. Katz, K. E. Niemeyer, and FORCE11 Software Citation
+Working Group, “Software citation principles,” PeerJ Comput. Sci., vol. 2,
+no. e86, 2016 (DOI )
+D. S. Katz, N. P. Chue Hong, T. Clark, et al., Recognizing the value of
+software: a software citation guide [version 2; peer review: 2 approved].
+F1000Research 2021, 9:1257 (DOI )
+N. P. Chue Hong, A. Allen, A. Gonzalez-Beltran, et al., Software Citation
+Checklist for Authors (Version 0.9.0). Zenodo. 2019a. (DOI )
+N. P. Chue Hong, A. Allen, A. Gonzalez-Beltran, et al., Software Citation
+Checklist for Developers (Version 0.9.0). Zenodo. 2019b. (DOI )
+
+
+Recommended format for software citation is to ensure the following information
+is provided as part of the reference (from Katz, Chue Hong, Clark,
+2021 which also contains
+software citation examples):
+
+Creator
+Title
+Publication venue
+Date
+Identifier
+Version
+Type
+
+
+
+Exercise/demo
+
+
Exercise
+
+We will add a CITATION.cff
file to our example repository.
+We will get a DOI using the Zenodo sandbox :
+
+
+We can try to create an example repository with a Jupyter Notebook and run it through Binder
+to make it persistent and citable.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/refactoring-concepts/index.html b/refactoring-concepts/index.html
new file mode 100644
index 0000000..9380b5a
--- /dev/null
+++ b/refactoring-concepts/index.html
@@ -0,0 +1,344 @@
+
+
+
+
+
+
+
+
+ Concepts in refactoring and modular code design — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+ Concepts in refactoring and modular code design
+
+ Edit on GitHub
+
+
+
+
+
+
+
+
+Concepts in refactoring and modular code design
+
+Starting questions for the collaborative document
+
+What does “modular code development” mean for you?
+What best practices can you recommend to arrive at well structured,
+modular code in your favourite programming language?
+What do you know now about programming that you wish somebody told you
+earlier?
+Do you design a new code project on paper before coding? Discuss pros and
+cons.
+Do you build your code top-down (starting from the big picture) or
+bottom-up (starting from components)? Discuss pros and cons.
+Would you prefer your code to be 2x slower if it was easier to read and
+understand?
+
+
+
+Pure functions
+
+Pure functions have no notion of state: They take input values and return
+values
+Given the same input, a pure function always returns the same value
+Function calls can be optimized away
+Pure function == data
+
+a) pure: no side effects
+def fahrenheit_to_celsius ( temp_f ):
+ temp_c = ( temp_f - 32.0 ) * ( 5.0 / 9.0 )
+ return temp_c
+
+temp_c = fahrenheit_to_celsius ( temp_f = 100.0 )
+print ( temp_c )
+
+
+b) stateful: side effects
+f_to_c_offset = 32.0
+f_to_c_factor = 0.555555555
+temp_c = 0.0
+
+def fahrenheit_to_celsius_bad ( temp_f ):
+ global temp_c
+ temp_c = ( temp_f - f_to_c_offset ) * f_to_c_factor
+
+fahrenheit_to_celsius_bad ( temp_f = 100.0 )
+print ( temp_c )
+
+
+Pure functions are easier to:
+
+Test
+Understand
+Reuse
+Parallelize
+Simplify
+Optimize
+Compose
+
+Mathematical functions are pure:
+
+\[f(x, y) = x - x^2 + x^3 + y^2 + xy\]
+
+\[(f \circ g)(x) = f(g(x))\]
+Unix shell commands are stateless:
+ $ cat somefile | grep somestring | sort | uniq | ...
+
+
+
+
+But I/O and network and disk and databases are not pure!
+
+
+
+
+
+
+From classes to functions
+Object-oriented programming and functional programming both have their place
+and value .
+Here is an example of expressing the same thing in Python in 4 different ways.
+Which one do you prefer?
+
+As a class :
+import math
+
+
+class Moon :
+ def __init__ ( self , name , radius , contains_water = False ):
+ self . name = name
+ self . radius = radius # in kilometers
+ self . contains_water = contains_water
+
+ def surface_area ( self ) -> float :
+ """Calculate the surface area of the moon assuming a spherical shape."""
+ return 4.0 * math . pi * self . radius ** 2
+
+ def __repr__ ( self ):
+ return f "Moon(name= { self . name !r} , radius= { self . radius } , contains_water= { self . contains_water } )"
+
+
+europa = Moon ( name = "Europa" , radius = 1560.8 , contains_water = True )
+
+print ( europa )
+print ( f "Surface area (km^2) of { europa . name } : { europa . surface_area () } " )
+
+
+
+As a dataclass :
+from dataclasses import dataclass
+import math
+
+
+@dataclass
+class Moon :
+ name : str
+ radius : float # in kilometers
+ contains_water : bool = False
+
+ def surface_area ( self ) -> float :
+ """Calculate the surface area of the moon assuming a spherical shape."""
+ return 4.0 * math . pi * self . radius ** 2
+
+
+europa = Moon ( name = "Europa" , radius = 1560.8 , contains_water = True )
+
+print ( europa )
+print ( f "Surface area (km^2) of { europa . name } : { europa . surface_area () } " )
+
+
+
+As a named tuple :
+import math
+from collections import namedtuple
+
+
+def surface_area ( radius : float ) -> float :
+ return 4.0 * math . pi * radius ** 2
+
+
+Moon = namedtuple ( "Moon" , [ "name" , "radius" , "contains_water" ])
+
+
+europa = Moon ( name = "Europa" , radius = 1560.8 , contains_water = True )
+
+print ( europa )
+print ( f "Surface area (km^2) of { europa . name } : { surface_area ( europa . radius ) } " )
+
+
+
+As a dict :
+import math
+
+
+def surface_area ( radius : float ) -> float :
+ return 4.0 * math . pi * radius ** 2
+
+
+europa = { "name" : "Europa" , "radius" : 1560.8 , "contains_water" : True }
+
+
+print ( europa )
+print ( f "Surface area (km^2) of { europa [ 'name' ] } : { surface_area ( europa [ 'radius' ]) } " )
+
+
+
+
+
+
+How to design your code before writing it
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/search/index.html b/search/index.html
new file mode 100644
index 0000000..d0e7c40
--- /dev/null
+++ b/search/index.html
@@ -0,0 +1,163 @@
+
+
+
+
+
+
+
+ Search — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/searchindex.js b/searchindex.js
new file mode 100644
index 0000000..1e85ff5
--- /dev/null
+++ b/searchindex.js
@@ -0,0 +1 @@
+Search.setIndex({"alltitles": {"(1) Basic browsing": [[22, "basic-browsing"]], "(1) Create a new branch and a new commit": [[21, "create-a-new-branch-and-a-new-commit"]], "(1) Navigate to your branch": [[24, "navigate-to-your-branch"]], "(1) Opening an issue": [[4, "opening-an-issue"]], "(2) Begin the pull request process": [[24, "begin-the-pull-request-process"]], "(2) Compare commit history with network graph": [[22, "compare-commit-history-with-network-graph"]], "(2) Create a new branch.": [[4, "create-a-new-branch"]], "(2) Modify the file again with a new commit": [[21, "modify-the-file-again-with-a-new-commit"]], "(3) Fill out and verify the pull request": [[24, "fill-out-and-verify-the-pull-request"]], "(3) How can you browse the history of a single file?": [[22, "how-can-you-browse-the-history-of-a-single-file"]], "(3) Make a change adding the recipe": [[4, "make-a-change-adding-the-recipe"]], "(3) Switch to the main branch and create a commit there": [[21, "switch-to-the-main-branch-and-create-a-commit-there"]], "(4) Browse the commits you just made": [[21, "browse-the-commits-you-just-made"]], "(4) Create the pull request": [[24, "create-the-pull-request"]], "(4) Push to GitHub as a new branch": [[4, "push-to-github-as-a-new-branch"]], "(4) Which files include the word \u201ctraining\u201d?": [[22, "which-files-include-the-word-training"]], "(5) Compare the branches": [[21, "compare-the-branches"]], "(5) Merge the pull request": [[24, "merge-the-pull-request"]], "(5) Open a pull request towards the main branch": [[4, "open-a-pull-request-towards-the-main-branch"]], "(5) Who modified a particular line last and when?": [[22, "who-modified-a-particular-line-last-and-when"]], "(6) Can you use this code yourself? Are you allowed to share modifications?": [[22, "can-you-use-this-code-yourself-are-you-allowed-to-share-modifications"]], "(6) Compare two arbitrary commits": [[21, "compare-two-arbitrary-commits"]], "(6) Delete merged branches": [[24, "delete-merged-branches"]], "(6) Reviewing pull requests": [[4, "reviewing-pull-requests"]], "(7) Contribute to the original repository with a pull request": [[24, "contribute-to-the-original-repository-with-a-pull-request"]], "(7) Draft pull requests": [[4, "draft-pull-requests"]], "(7) Renaming a branch": [[21, "renaming-a-branch"]], "(8) Creating a tag": [[21, "creating-a-tag"]], "AI tools open up a box of questions which are beyond our scope here": [[9, null]], "Adding the unit test to GitHub Actions": [[19, "adding-the-unit-test-to-github-actions"]], "Additional resources": [[15, "additional-resources"]], "Analogy: Imagine we want to optimize the London Underground (subway) system": [[15, "discussion-1"]], "Automated testing": [[19, null]], "Avoiding conflicts": [[23, "avoiding-conflicts"]], "Background": [[21, "background"]], "Best practice: Install dependencies into isolated environments": [[6, "best-practice-install-dependencies-into-isolated-environments"]], "Big-picture goal": [[10, "big-picture-goal"]], "But I/O and network and disk and databases are not pure!": [[17, "but-i-o-and-network-and-disk-and-databases-are-not-pure"]], "Choosing a software license": [[18, null]], "Choosing an installation method": [[11, "choosing-an-installation-method"]], "Choosing the right system size": [[15, "choosing-the-right-system-size"]], "Cloning a repository": [[2, "cloning-a-repository"]], "Code formatting": [[13, "code-formatting"]], "Collaborating within the same repository": [[4, null]], "Collaborative version control and code review": [[0, null]], "Commits, branches, repositories, forks, clones": [[2, "commits-branches-repositories-forks-clones"]], "Concepts around collaboration": [[2, null]], "Concepts in refactoring and modular code design": [[17, null]], "Conda": [[6, null]], "Conda ecosystem explained": [[6, "conda-ecosystem-explained"]], "Conflict resolution": [[23, null]], "Confused about reStructuredText vs. Markdown vs. MyST?": [[7, "confused-about-restructuredtext-vs-markdown-vs-myst"]], "Consider annotating your functions with type hints": [[9, "consider-annotating-your-functions-with-type-hints"]], "Consider using AI-assisted coding": [[9, "consider-using-ai-assisted-coding"]], "Containers": [[6, "containers"]], "Copyright and derivative work: Sampling/remixing": [[18, "copyright-and-derivative-work-sampling-remixing"]], "Creating a Python package and deploying it to PyPI": [[14, null]], "Creating a Python package with the help of Flit": [[14, "creating-a-python-package-with-the-help-of-flit"]], "Creating a copy of the repository by \u201cforking\u201d or \u201ccloning\u201d": [[22, "creating-a-copy-of-the-repository-by-forking-or-cloning"]], "Creating branches and commits": [[21, null]], "Credit": [[5, null]], "Cross-referencing issues and pull requests": [[4, "cross-referencing-issues-and-pull-requests"]], "Debugging with print statements": [[9, "debugging-with-print-statements"]], "Demo: Building documentation with GitHub Actions": [[7, "demo-building-documentation-with-github-actions"]], "Demonstration": [[25, "demonstration"]], "Different ways to share a notebook": [[12, "different-ways-to-share-a-notebook"]], "Discussion": [[8, "discussion-0"], [15, "discussion-0"], [16, "discussion-1"], [18, "discussion-0"], [25, "discussion-0"]], "Discussion and goals": [[8, "discussion-and-goals"]], "Discussion: Explain how you currently cite software": [[16, "discussion-0"]], "Di\u00e1taxis": [[7, "diataxis"]], "Document dependencies": [[12, "document-dependencies"]], "End-to-end tests": [[19, "end-to-end-tests"]], "Enumerate if you need the index": [[9, "enumerate-if-you-need-the-index"]], "Example project: 2D classification task using a nearest-neighbor predictor": [[8, null]], "Example run": [[8, "example-run"]], "Exercise": [[1, "exercise"], [3, "exercise"], [4, "exercise"], [16, "exercise-0"], [18, "exercise-0"], [19, "exercise-0"], [24, "exercise"], [27, "exercise"]], "Exercise preparation": [[1, "prerequisites-0"], [3, "prerequisites-0"], [4, "prerequisites-0"], [22, "prerequisites-0"]], "Exercise/demo": [[16, "exercise-demo"], [18, "exercise-demo"]], "Exercise/demo: Making your notebooks reproducible by anyone (15 min)": [[12, "exercise-0"]], "Exercise: Browsing an existing project (20 min)": [[22, "exercise-0"]], "Exercise: Collaborating within the same repository (25 min)": [[3, "exercise-0"], [4, "exercise-0"]], "Exercise: Copy and browse an existing project": [[22, "exercise-copy-and-browse-an-existing-project"]], "Exercise: Creating branches and commits": [[21, "exercise-creating-branches-and-commits"]], "Exercise: Licensing situations": [[18, "exercise-2"]], "Exercise: Merging branches": [[24, "exercise-0"]], "Exercise: Practice creating commits and branches (20 min)": [[21, "exercise-0"]], "Exercise: Practicing code review (25 min)": [[1, "exercise-0"]], "Exercise: Practicing profiling": [[15, "exercise-0"]], "Exercise: Set up a Sphinx documentation": [[7, "exercise-set-up-a-sphinx-documentation"], [7, "exercise-0"]], "Exercise: Turn your project to a Git repo and share it (20 min)": [[27, "exercise-0"]], "Exercise: What constitutes derivative work?": [[18, "exercise-1"]], "Exercises": [[15, "exercises"], [19, "exercises"]], "Features: roll-back, branching, merging, collaboration": [[25, "features-roll-back-branching-merging-collaboration"]], "Follow the PEP 8 style guide": [[9, "follow-the-pep-8-style-guide"]], "Forking a repository": [[2, "forking-a-repository"]], "Forking, cloning, and browsing": [[22, null]], "From classes to functions": [[17, "from-classes-to-functions"]], "GitHub, VS Code, or command line": [[22, "github-vs-code-or-command-line"]], "Great resources": [[16, null]], "Help and discussion": [[1, "help-and-discussion"], [3, "help-and-discussion"]], "Help! I don\u2019t have permissions to push my local changes": [[3, "help-i-don-t-have-permissions-to-push-my-local-changes"]], "How about staging and committing?": [[26, "how-about-staging-and-committing"]], "How large should a commit be?": [[26, "how-large-should-a-commit-be"]], "How testing is often taught": [[19, "how-testing-is-often-taught"]], "How to approach other people\u2019s repositories with ideas, changes, and requests": [[3, "how-to-approach-other-peoples-repositories-with-ideas-changes-and-requests"]], "How to ask for changes in a pull request": [[1, "how-to-ask-for-changes-in-a-pull-request"]], "How to avoid: \u201cIt works on my machine \ud83e\udd37\u201d": [[6, "how-to-avoid-it-works-on-my-machine"]], "How to cite software": [[16, "how-to-cite-software"]], "How to communicate the dependencies as part of a report/thesis/publication": [[6, "how-to-communicate-the-dependencies-as-part-of-a-report-thesis-publication"]], "How to contribute changes to repositories that belong to others": [[3, null]], "How to create a fork": [[22, null]], "How to design your code before writing it": [[17, "how-to-design-your-code-before-writing-it"]], "How to get a digital object identifier (DOI)": [[12, "how-to-get-a-digital-object-identifier-doi"]], "How to install dependencies into environments": [[6, "how-to-install-dependencies-into-environments"]], "How to make your software citable": [[16, "how-to-make-your-software-citable"]], "How to modify a pull request to address the review comments": [[1, "how-to-modify-a-pull-request-to-address-the-review-comments"]], "How to publish your code": [[16, null]], "How to suggest a change in a pull request as a reviewer": [[1, "how-to-suggest-a-change-in-a-pull-request-as-a-reviewer"]], "How to update your fork with changes from upstream": [[3, "how-to-update-your-fork-with-changes-from-upstream"]], "How to verify your installation": [[11, "how-to-verify-your-installation"]], "If you have a tool/method that works for you, keep using it": [[11, null]], "In-code documentation": [[7, "in-code-documentation"]], "Installing Python via Miniforge": [[11, "installing-python-via-miniforge"]], "Installing and activating the software environment": [[11, "installing-and-activating-the-software-environment"]], "Instructor note": [[7, "instructor-note-0"], [7, "instructor-note-1"], [8, "instructor-note-0"], [13, "instructor-note-0"], [19, "instructor-note-0"], [22, "instructor-note-0"]], "Introduction to version control with Git and GitHub": [[20, null]], "Is putting software on GitHub/GitLab/\u2026 publishing?": [[16, "is-putting-software-on-github-gitlab-publishing"], [27, "is-putting-software-on-github-gitlab-publishing"]], "Iterating": [[9, "iterating"], [9, "id1"]], "Keypoints": [[6, "keypoints-0"], [6, "keypoints-1"], [7, "keypoints-0"]], "Know your collections": [[9, "know-your-collections"]], "Learning goals": [[8, null]], "Linters and formatters can be configured to your liking": [[9, "discussion-0"]], "Linting and static type checking": [[9, "linting-and-static-type-checking"]], "List comprehensions, map, and filter instead of loops": [[9, "list-comprehensions-map-and-filter-instead-of-loops"]], "Making functions more ergonomic": [[9, "making-functions-more-ergonomic"]], "Managing dependencies on a supercomputer": [[6, "managing-dependencies-on-a-supercomputer"]], "Many languages allow \u201cdocstrings\u201d": [[7, "many-languages-allow-docstrings"]], "Many tools exist": [[15, "many-tools-exist"]], "Measure instead of guessing": [[15, "measure-instead-of-guessing"]], "Merging changes and contributing to the project": [[24, null]], "Monday": [[10, "monday"]], "More exercises": [[18, "more-exercises"]], "More resources": [[16, "more-resources"], [18, "more-resources"]], "Motivation": [[3, null], [3, null], [19, "motivation"], [25, null]], "Objectives": [[2, "objectives-0"], [6, "objectives-0"], [7, "objectives-0"], [9, "objectives-0"], [12, "objectives-0"], [14, "objectives-0"], [15, "objectives-0"], [16, "objectives-0"], [18, "objectives-0"], [19, "objectives-0"], [21, "objectives-0"], [22, "objectives-0"], [24, "objectives-0"], [25, "objectives-0"], [27, "objectives-0"]], "Often a README is enough - checklist": [[7, "often-a-readme-is-enough-checklist"]], "Often you can avoid using indices": [[9, "often-you-can-avoid-using-indices"]], "One of the simplest tools is to insert timers": [[15, "one-of-the-simplest-tools-is-to-insert-timers"]], "Opening a pull request towards the upstream repository": [[3, "opening-a-pull-request-towards-the-upstream-repository"]], "Optional: How to auto-generate API documentation in Python": [[7, "optional-how-to-auto-generate-api-documentation-in-python"]], "Optional: How to turn your project to a Git repo and share it": [[27, null]], "Other useful tooling for notebooks": [[13, null]], "Papers with focus on scientific software": [[16, "papers-with-focus-on-scientific-software"]], "Parallelizing": [[9, "parallelizing"]], "Pinning package versions": [[6, "pinning-package-versions"]], "Possibilities to host Sphinx documentation": [[7, "possibilities-to-host-sphinx-documentation"]], "Practical advice: How much Git is necessary?": [[26, null]], "Practicing code review": [[1, null]], "Prefer catch-all unpacking over indexing/slicing": [[9, "prefer-catch-all-unpacking-over-indexing-slicing"]], "Preparation": [[7, "prerequisites-0"], [10, "prerequisites-0"]], "Profiling": [[15, null]], "Project structure": [[9, "project-structure"]], "Pull requests can be coupled with automated testing": [[3, "pull-requests-can-be-coupled-with-automated-testing"]], "Pure functions": [[17, "pure-functions"]], "PyPI": [[6, null]], "Pytest": [[19, "pytest"]], "Python, conda, anaconda, miniforge, etc?": [[11, null]], "Reading and writing files": [[9, "reading-and-writing-files"]], "Recovery": [[3, "solution-0"]], "Removing the software environment": [[11, "removing-the-software-environment"]], "Reproducible environments and dependencies": [[6, null]], "Reproducible research software development using Python (ML example)": [[10, null]], "Resolving a conflict (demonstration)": [[23, "resolving-a-conflict-demonstration"]], "Schedule": [[10, "schedule"]], "Searching in a forked repository will not work instantaneously!": [[22, null]], "Sharing dynamic notebooks using Binder": [[12, "sharing-dynamic-notebooks-using-binder"]], "Sharing notebooks": [[12, null]], "Should we even optimize the code?": [[15, "should-we-even-optimize-the-code"]], "Software install instructions": [[11, null]], "Solution": [[15, "solution-0"], [18, "solution-0"], [18, "solution-1"], [25, "solution-0"]], "Solution and hints": [[4, "solution-and-hints"]], "Solution and walk-through": [[21, "solution-and-walk-through"], [22, "solution-and-walk-through"], [24, "solution-and-walk-through"]], "Starting JupyterLab": [[11, "starting-jupyterlab"]], "Starting questions for the collaborative document": [[17, "starting-questions-for-the-collaborative-document"]], "Step 1: Initialize the package metadata and try a local install": [[14, "step-1-initialize-the-package-metadata-and-try-a-local-install"]], "Step 2: Testing an install from GitHub": [[14, "step-2-testing-an-install-from-github"]], "Step 3: Deploy the package to test-PyPI using GitHub Actions": [[14, "step-3-deploy-the-package-to-test-pypi-using-github-actions"]], "Summary": [[1, "summary"], [3, "summary"], [4, "summary"], [21, "summary"], [22, "summary"], [24, "summary"]], "Synchronizing changes between repositories": [[2, "synchronizing-changes-between-repositories"]], "Talking about code": [[25, "talking-about-code"]], "Taxonomy of software licenses": [[18, "taxonomy-of-software-licenses"]], "The human side of conflicts": [[23, "discussion-0"]], "There are many choices and we try to suggest a good compromise": [[11, "discussion-0"]], "This will be a demo": [[14, null]], "Thursday": [[10, "thursday"]], "Tools and distributions for dependency management in Python": [[6, "tools-and-distributions-for-dependency-management-in-python"]], "Tools and useful practices": [[9, null]], "Tracing profilers vs. sampling profilers": [[15, "tracing-profilers-vs-sampling-profilers"]], "Tuesday": [[10, "tuesday"]], "Two ecosystems: PyPI (The Python Package Index) and Conda": [[6, "two-ecosystems-pypi-the-python-package-index-and-conda"]], "Unpacking": [[9, "unpacking"]], "Updating environments": [[6, "updating-environments"]], "Use an auto-formatter": [[9, "use-an-auto-formatter"]], "Use relative paths and pathlib": [[9, "use-relative-paths-and-pathlib"]], "Use subprocess instead of os.system": [[9, "use-subprocess-instead-of-os-system"]], "We will not focus on \u2026": [[8, null]], "Wednesday": [[10, "wednesday"]], "What else is possible": [[19, "what-else-is-possible"]], "What if you have more than just one script?": [[14, "discussion-0"]], "What if you need more than a README?": [[7, "what-if-you-need-more-than-a-readme"]], "What is a protected branch? And how to modify it?": [[4, "what-is-a-protected-branch-and-how-to-modify-it"]], "What level of branching complexity is necessary for each project?": [[26, "what-level-of-branching-complexity-is-necessary-for-each-project"]], "What to avoid": [[26, "what-to-avoid"]], "What we typically like to snapshot": [[25, "what-we-typically-like-to-snapshot"]], "Where to read more": [[7, "where-to-read-more"]], "Where to start": [[19, "where-to-start"]], "Where to start with documentation": [[7, null]], "Why do we need to keep track of versions?": [[25, "why-do-we-need-to-keep-track-of-versions"]], "Why software licenses matter": [[18, "why-software-licenses-matter"]], "Why? \ud83d\udc97\u2709\ufe0f to your future self": [[7, "why-to-your-future-self"]], "Working on the command line? Use \u201cgit status\u201d all the time": [[26, "working-on-the-command-line-use-git-status-all-the-time"]], "Writing useful commit messages": [[26, "writing-useful-commit-messages"]], "Zip if you need to iterate over two collections": [[9, "zip-if-you-need-to-iterate-over-two-collections"]]}, "docnames": ["collaboration", "collaboration/code-review", "collaboration/concepts", "collaboration/forking-workflow", "collaboration/same-repository", "credit", "dependencies", "documentation", "example", "good-practices", "index", "installation", "notebooks/sharing", "notebooks/tooling", "packaging", "profiling", "publishing", "refactoring-concepts", "software-licensing", "testing", "version-control", "version-control/branching-and-committing", "version-control/browsing", "version-control/conflict-resolution", "version-control/merging", "version-control/motivation", "version-control/practical-advice", "version-control/sharing"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2}, "filenames": ["collaboration.md", "collaboration/code-review.md", "collaboration/concepts.md", "collaboration/forking-workflow.md", "collaboration/same-repository.md", "credit.md", "dependencies.md", "documentation.md", "example.md", "good-practices.md", "index.md", "installation.md", "notebooks/sharing.md", "notebooks/tooling.md", "packaging.md", "profiling.md", "publishing.md", "refactoring-concepts.md", "software-licensing.md", "testing.md", "version-control.md", "version-control/branching-and-committing.md", "version-control/browsing.md", "version-control/conflict-resolution.md", "version-control/merging.md", "version-control/motivation.md", "version-control/practical-advice.md", "version-control/sharing.md"], "indexentries": {}, "objects": {}, "objnames": {}, "objtypes": {}, "terms": {"": [1, 4, 6, 11, 12, 15, 16, 17, 18, 19, 21, 22, 23, 24, 26, 27], "0": [7, 8, 9, 16, 17, 18, 19, 21, 26, 27], "00": 10, "04": [7, 14, 19], "08": [16, 27], "09": [10, 25], "0e": 19, "1": [7, 8, 9, 10, 15, 16, 19], "10": [10, 16, 18, 19], "100": [17, 19, 25, 27], "10000000": 9, "100k": 6, "11": [10, 16], "1121": 16, "11554001": 18, "12": [6, 7, 10, 11, 14, 19, 27], "123": 26, "1234": 16, "1257": 16, "13": [9, 10], "14": 10, "15": [7, 10, 19, 26], "1560": 17, "17": 27, "19": 25, "19k": 27, "2": [7, 9, 10, 15, 16, 17, 19, 26, 27], "20": [10, 15, 19], "200": 6, "2013": 7, "2016": 16, "2019": 25, "2019a": 16, "2019b": 16, "2020": 25, "2021": [16, 18, 25], "2023": [11, 25], "2024": 11, "2025": [7, 25], "21": [25, 27], "24": [7, 14, 19], "2600": 15, "273": 7, "2799": 18, "28": 27, "28k": 6, "2d": 10, "2x": 17, "3": [6, 7, 9, 11, 17, 18, 19, 27], "30": [7, 9, 10], "32": [17, 19], "35": 10, "36": 27, "37": 19, "4": [9, 14, 16, 17, 18, 27], "40": 10, "42": 21, "45": 10, "5": [7, 9, 15, 17, 19], "50": [7, 8, 26], "5000": 3, "5281": [16, 18], "555555555": 17, "5678": 16, "6": [9, 19, 23, 27], "7": [8, 9, 27], "77160": 18, "777777": 19, "79ce3b": 22, "79ce3be8": 25, "8": [15, 17], "9": [3, 9, 16, 17, 19], "90": 15, "9101": 16, "94": 8, "A": [1, 2, 4, 6, 9, 10, 14, 16, 18, 19, 21, 22, 23, 26], "And": [1, 9, 18], "As": [1, 9, 14, 15, 17, 18], "At": [6, 7, 12, 22], "BY": [5, 16, 18], "Be": [2, 4, 8, 27], "Being": [1, 3, 4, 6, 24], "But": [1, 3, 4, 9, 15, 22, 24, 25, 26, 27], "By": [6, 18, 22, 24], "For": [4, 6, 7, 9, 11, 15, 18, 21, 27], "If": [1, 3, 4, 6, 7, 9, 10, 12, 14, 15, 16, 18, 19, 21, 22, 24, 26, 27], "In": [1, 2, 3, 4, 6, 8, 10, 11, 12, 14, 15, 16, 18, 19, 21, 22, 23, 24, 25, 27], "Into": 11, "It": [1, 2, 3, 4, 7, 8, 9, 11, 14, 15, 19, 22, 24, 25, 26, 27], "No": 16, "Not": [6, 7, 15, 18, 21, 24, 26], "On": [9, 21, 22, 23, 24, 26], "One": [8, 24], "Or": [1, 3, 6, 9, 25, 26], "The": [1, 2, 3, 4, 5, 7, 8, 9, 12, 15, 16, 18, 19, 21, 22, 24, 26, 27], "Then": [3, 4, 6, 7, 8, 11, 21, 24, 27], "There": [6, 7, 9, 14, 24], "These": [3, 6, 9, 22, 25], "To": [3, 4, 9, 14, 19, 22, 27], "Will": 9, "With": [9, 21, 25], "__init__": [14, 17], "__main__": [9, 15], "__name__": [9, 15], "__pycache__": 26, "__repr__": 17, "__token__": 14, "__version__": 14, "_build": 7, "_result": 15, "aaltoscicomp": [6, 11, 15], "ab": 19, "abid": 6, "abl": [1, 2, 3, 4, 6, 7, 8, 18, 24, 27], "about": [1, 3, 4, 9, 12, 14, 16, 17, 18, 21, 27], "abov": [1, 8, 11, 15, 18, 19, 21, 22, 24, 27], "absolut": 9, "academia": 6, "accept": [1, 3, 4, 8, 26], "access": [2, 3, 4, 12, 16, 18, 22, 27], "accident": [1, 4, 27], "accord": 9, "account": [2, 3, 4, 10, 14, 22, 27], "accuraci": 8, "achiev": [21, 24], "across": [2, 24], "action": 3, "activ": [4, 6, 15], "actual": [10, 21, 22, 24], "ad": [1, 3, 7, 9, 12, 15, 16, 21, 24], "adapt": [11, 12, 14, 15, 16], "add": [1, 4, 7, 9, 14, 15, 16, 18, 19, 21, 22, 24, 26, 27], "addit": [6, 7, 11, 13, 16, 21, 27], "address": [4, 27], "adipisc": 7, "adjust": [3, 4, 7, 8, 9, 21, 24, 26], "advanc": [22, 24], "advantag": [6, 8, 15], "advic": [10, 20, 21], "affect": [6, 9, 11, 18, 25], "after": [1, 3, 11, 12, 15, 22, 23, 24, 27], "again": [1, 3, 4, 19, 24, 26], "against": 18, "agenda": 18, "agpl": 18, "agreement": 3, "aim": 6, "al": 16, "algorithm": [8, 18], "alia": 22, "alic": 9, "aliqua": 7, "aliquip": 7, "all": [2, 4, 5, 6, 7, 10, 11, 13, 14, 15, 18, 19, 21, 22, 23, 27], "allen": 16, "allianc": 18, "allow": [4, 11, 12, 18, 24], "almost": [7, 17, 18], "along": [6, 10, 22], "alpha": 26, "alreadi": [3, 15, 19, 21, 22, 27], "also": [1, 2, 3, 4, 6, 7, 9, 11, 12, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24, 26, 27], "altair": [6, 7, 11, 18, 19], "altern": [2, 4, 6, 9, 14, 15, 22], "alwai": [1, 3, 11, 17, 18, 24, 25, 26], "am": [9, 25], "ambiti": 26, "amend": 1, "amet": 7, "among": 6, "amount": 15, "an": [1, 3, 6, 7, 10, 12, 13, 15, 16, 17, 18, 19, 21, 23, 24, 25, 27], "anaconda": 6, "analysi": 6, "analyz": 9, "ani": [3, 7, 11, 19, 21, 23, 26, 27], "anim": 7, "annot": [21, 22, 25], "anoth": [7, 11, 14, 18, 21, 22, 24], "answer": [6, 14, 21, 22, 24, 25], "anticip": 25, "anymor": [6, 24], "anyon": [11, 22], "anyth": [18, 24], "apach": 18, "api": 9, "appear": [15, 21], "append": [9, 15], "appli": [8, 10, 18], "applic": 22, "apprentic": 9, "approach": [7, 8, 17], "appropri": 16, "approv": [14, 16], "approxim": 15, "apptain": 6, "ar": [1, 2, 3, 4, 6, 7, 8, 10, 14, 15, 16, 18, 19, 21, 23, 24, 25, 26, 27], "arbitrari": 19, "area": [17, 26], "aren": [21, 22, 24], "argument": [6, 8, 9, 19, 21], "arm": 11, "arm64": 11, "around": [0, 10, 16, 18, 21, 24], "arriv": 17, "arrow": [18, 24, 27], "articl": [4, 6], "ask": [3, 4, 6, 15, 18, 23], "assert": [7, 19], "assign": 16, "assist": 18, "assum": [10, 17, 19, 24], "attach": [2, 6, 21], "attent": 3, "attract": 18, "attribut": 18, "august": [7, 25], "aut": 7, "authent": [1, 3, 4], "author": [7, 14, 16, 18, 22, 25, 26, 27], "autoapi": [6, 7, 11], "autoapi_dir": 7, "autoapi_ignor": 7, "autobuild": [6, 7, 11], "autoflak": 9, "autom": 10, "automat": [1, 2, 3, 4, 7, 9, 11, 19, 21], "avail": [6, 11, 15, 18, 19], "avoid": 22, "awai": [17, 26], "awar": 22, "azur": 12, "b": [7, 9, 15, 17, 18, 19, 27], "back": [2, 15, 19, 21, 22], "backend": 14, "background": 11, "backup": [12, 27], "backward": 9, "bad": [25, 26], "badg": 12, "badli": 9, "base": [2, 8, 9, 11, 12, 15, 19, 24, 26], "bash": [7, 11, 19, 22], "basi": [4, 11, 24], "basic": [21, 24], "basicconfig": 9, "bast": 18, "batch": 15, "becam": 7, "becaus": [15, 16, 24, 26, 27], "becom": 22, "been": [23, 24], "befor": [3, 4, 9, 14, 15, 18, 21, 22, 23, 24, 25, 26], "begin": [7, 9, 10], "beginn": [11, 15], "behind": [3, 8], "being": [3, 9, 12, 18], "belong": [0, 8, 10], "below": [1, 3, 4, 6, 7, 9, 11, 15, 16, 21, 22, 24, 27], "beltran": 16, "best": [9, 17, 18, 23], "better": [1, 6, 7, 9, 11, 15, 19, 22, 25, 26], "between": [4, 6, 9, 21, 23, 25], "bewar": 9, "bias": 18, "big": [8, 14, 17, 22], "bin": 11, "binder": [6, 16], "bioconda": [6, 11], "bisect": 9, "bit": 6, "black": [6, 9, 11, 13], "blame": 22, "blob": [19, 25], "block": [7, 9, 10], "blog": [6, 16], "blogdown": 7, "blue": 11, "bob": 9, "book": [3, 4, 9, 15], "bookdown": 7, "bool": [9, 17], "borrow": 6, "both": [3, 4, 6, 9, 15, 17, 21, 22], "bottleneck": 15, "bottom": [4, 8, 17, 21, 22, 24], "box": 24, "branch": [1, 3, 7, 10, 14, 19, 20, 22, 23, 27], "brand": 27, "break": [9, 10, 19, 25, 26], "brett": 9, "brief": 21, "bring": [15, 24], "brittl": 9, "broke": [19, 25], "brows": [6, 8, 9, 10, 15, 19, 20, 24, 25, 26], "browser": [7, 11, 12, 15, 21, 22], "bsd": 18, "bsd3": 18, "buffer": 9, "bug": [18, 19, 25], "bugfix": 18, "build": [6, 10, 14, 17, 18, 19, 21, 22], "buildapi": 14, "built": 7, "bunch": [22, 27], "bundl": 6, "button": [1, 12, 13, 21, 22, 23, 24, 27], "c": [6, 16, 18, 22], "c7f0e8bfc718be04525847fc7ac237f470add76": 2, "cach": [7, 15, 19], "calcul": 17, "call": [2, 4, 6, 11, 14, 15, 16, 17, 21, 22, 24, 27], "can": [1, 2, 4, 6, 7, 8, 11, 12, 14, 15, 16, 17, 18, 19, 21, 23, 24, 25, 26, 27], "cannot": [4, 18], "caption": 7, "captur": 15, "cargo": 6, "carri": 25, "case": [1, 2, 3, 4, 7, 15, 18, 19, 22, 23, 24, 27], "cast": 9, "cat": 17, "catch": 19, "caus": [6, 23], "cc": [5, 16, 18], "cd": [7, 22], "cell": [11, 13], "celsiu": [7, 19], "central": 2, "certain": [21, 25], "cff": 16, "cffinit": 16, "challeng": [6, 11], "chanc": 23, "chang": [0, 6, 7, 9, 10, 11, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27], "changelog": 26, "channel": [6, 7, 11, 14, 19], "charg": 12, "charli": 9, "chart": 8, "chase": 19, "chat": 9, "check": [3, 4, 6, 7, 12, 15, 19, 24, 27], "checker": [9, 18], "checklist": [4, 16], "checkout": [7, 14, 19], "checksum": 25, "chocol": 9, "choic": [18, 26], "choos": [8, 9, 10, 23, 24, 27], "chose": 27, "chue": 16, "chunk": 9, "ci": [7, 19], "cillum": 7, "circ": 17, "circular": 8, "circumst": 23, "citabl": 27, "citat": [7, 16, 19], "cite": 18, "clark": 16, "class": [8, 22], "classic": 15, "classif": [5, 7, 10, 19, 21, 22, 25], "classifi": [8, 14], "clean": [17, 18], "cleanup": [7, 19], "clear": [3, 16, 18], "clearli": 16, "click": [1, 3, 4, 6, 7, 11, 12, 14, 15, 19, 21, 22, 23, 24, 27], "clone": [3, 4, 7, 10, 20, 25], "close": [1, 3, 4, 11], "cloud": [4, 12], "cluster": 6, "cmakelist": 6, "cocalc": 12, "code": [2, 3, 4, 6, 8, 10, 11, 12, 14, 18, 19, 21, 24, 26, 27], "codecov": 19, "coderefineri": [5, 6, 7, 11, 12], "colab": 12, "collabor": [1, 10, 12, 23, 24, 27], "colleagu": [2, 9], "collect": [4, 11, 17, 22, 26], "colon_f": 7, "com": [3, 4, 5, 6, 7, 11, 14, 19, 21, 22, 25, 26, 27], "combin": [1, 18, 23, 24, 26], "come": [4, 6, 9, 18, 19, 22, 24], "comfort": 22, "comic": 6, "command": [2, 4, 6, 8, 9, 11, 17, 21, 23, 24, 27], "commemor": 2, "comment": [4, 7, 9, 18, 24], "commentari": 7, "commerci": [11, 18], "commiss": 18, "commit": [1, 3, 4, 9, 10, 20, 23, 24, 25, 27], "commodo": 7, "common": [1, 2, 5, 6, 14, 18, 21], "commonli": 11, "commun": [2, 4, 11, 14, 18], "compact": 9, "compani": [6, 11, 18], "compar": [7, 9, 18, 19, 25, 26], "compat": [6, 18], "compil": 18, "complet": [2, 9, 15, 18, 26, 27], "complex": [11, 14], "complic": [6, 19], "compon": [17, 18, 19], "compos": 17, "compress": 27, "comput": [2, 3, 6, 8, 10, 11, 16, 22, 25, 27], "con": [6, 11, 15, 17], "concept": [0, 9, 10], "conceptu": 25, "concret": 14, "conda": [7, 14, 19], "condit": [18, 19], "conf": 7, "confid": 23, "config": 22, "configur": [4, 11, 15, 25, 27], "conflict": [6, 10, 20], "connect": [4, 10], "consectetur": 7, "consequat": 7, "consid": [4, 18, 26, 27], "consist": [9, 27], "construct": [1, 4, 7, 9, 10], "consum": 15, "contact": 18, "contain": [2, 3, 11, 14, 15, 16, 24, 26, 27], "container": 6, "contains_wat": 17, "content": [7, 9, 18], "context": [1, 18, 22, 26], "continu": [1, 22, 24], "contribut": [0, 2, 4, 7, 10, 18, 20], "control": [7, 9, 10, 18, 21, 22, 24, 25, 27], "convent": 26, "converg": 26, "convert": [6, 7, 9, 11, 12, 19], "coordin": 9, "copi": [2, 4, 7, 11, 18, 21, 25, 27], "copyleft": 18, "copyright": [4, 7], "corner": 4, "correct": [3, 27], "correctli": [8, 17], "correspond": [14, 19, 22], "cost": 21, "could": [1, 2, 4, 11, 18, 21, 22, 27], "count": [15, 27], "count_unique_word": 9, "count_unique_words1": 15, "count_unique_words2": 15, "count_unique_words3": 15, "counter": 1, "coupl": 7, "cours": [6, 10, 11, 15, 22], "cov": 19, "cover": [7, 24], "coverag": 19, "coveral": 19, "cprofil": 15, "cpu": 15, "crash": 11, "cream": 9, "creat": [1, 2, 3, 6, 7, 9, 10, 11, 12, 15, 16, 18, 19, 20, 23, 25, 26, 27], "creation": 6, "creativ": 18, "creator": 16, "credit": 16, "cross": [3, 26], "crowd": 15, "csv": [8, 9], "culpa": 7, "cupidatat": 7, "current": [6, 15, 21, 24, 27], "custom": 18, "cycl": 9, "cyclic": 9, "d": [16, 18, 24], "dai": [4, 15, 19, 23, 26], "danger": [26, 27], "dashboard": 12, "dask": 9, "data": [2, 8, 9, 15, 17, 18, 19, 23, 24, 25], "dataclass": [9, 17], "date": [3, 16, 23, 25], "david": 9, "db": 7, "deal": 6, "debrief": 10, "decad": 26, "decid": [2, 23], "declar": 23, "decor": [21, 22], "deepnot": 12, "def": [7, 9, 15, 17, 19], "default": [3, 6, 9, 18, 21, 22, 24, 25, 27], "defaultdict": 9, "defin": [3, 6, 21, 22], "definit": 6, "degre": 7, "delai": 15, "delet": [4, 16], "deliber": 1, "delta": 27, "demo": 10, "demonstr": [3, 8, 9, 12, 22], "depend": [4, 7, 9, 10, 11, 14, 15, 18, 19, 22], "deploi": [7, 10, 19], "deprec": 9, "deprecationwarn": 9, "dequ": 9, "describ": [3, 4, 11, 16, 19, 22, 24], "descript": [4, 6, 14, 24, 27], "deserunt": 7, "design": [10, 11, 18, 19, 21, 22, 24, 26], "desk": 9, "detail": [1, 3, 7, 8, 15, 18, 26], "detect": [9, 15, 26], "determinist": 15, "develop": [2, 6, 16, 17, 21, 24, 26], "diataxi": 7, "dict": [9, 17], "dictionari": 9, "did": [6, 16, 21, 25], "didn": [15, 21], "diff": 21, "differ": [2, 3, 6, 8, 9, 11, 15, 17, 18, 21, 22, 23, 24, 25, 27], "difficult": [3, 9, 11, 19, 24], "digit": 16, "dimens": 15, "direct": [3, 4, 7, 24], "directli": [1, 4, 6, 21], "director": 18, "directori": [6, 7, 14, 19, 22, 25, 26, 27], "disadvantag": 15, "discard": 25, "discov": 25, "discuss": [2, 6, 7, 9, 10, 12, 17, 19, 22, 23, 24, 26], "disk": [6, 21], "distribut": [8, 11, 18], "diverg": 21, "divers": 9, "divid": 2, "do": [1, 2, 3, 4, 6, 7, 9, 10, 11, 15, 16, 17, 18, 19, 21, 22, 23, 24, 26, 27], "doc": [6, 7, 12, 25], "doconc": 12, "docstr": [9, 14], "doctor": 15, "document": [6, 10, 16, 21, 25], "doe": [3, 6, 7, 9, 11, 14, 15, 16, 17, 18, 19, 21, 23], "doesn": [11, 15], "doi": [16, 18, 27], "dolor": 7, "don": [1, 4, 6, 7, 9, 10, 11, 15, 18, 21, 22, 23, 24, 27], "donald": 15, "done": [3, 4, 9, 21, 22, 24, 27], "dot": [21, 22, 24], "doubl": 3, "down": [4, 12, 15, 17, 24, 27], "download": [2, 6, 11, 15, 18], "draft": [18, 24], "drive": [6, 12], "driven": 17, "driver": 18, "drop": 6, "ds_store": 7, "due": 6, "dui": 7, "duplic": 9, "durat": 15, "dure": [1, 3, 6, 11, 18, 23, 26], "dynam": [14, 18], "e": [2, 6, 7, 9, 12, 15, 16, 18, 21, 22], "e86": 16, "ea": 7, "each": [1, 2, 3, 4, 6, 8, 11, 21, 22], "earli": [4, 23, 26], "earlier": 17, "eas": 6, "easi": [1, 6, 14, 16, 17, 18, 21, 22, 27], "easier": [6, 7, 9, 14, 17, 19, 24, 26], "easiest": 18, "easili": [6, 21, 26], "edit": [12, 16, 21], "editor": [9, 10, 22, 23, 24], "effect": [9, 17, 24], "effici": 8, "effort": [4, 15], "egi": 12, "either": [1, 6, 9, 12, 21, 22, 27], "eiusmod": 7, "el": [7, 19], "elabor": 9, "element": [9, 15], "elit": 7, "els": [1, 4, 9, 18, 23, 26], "email": [4, 14], "employ": 18, "employe": 6, "empti": [26, 27], "emul": 7, "en": 4, "enabl": [1, 26], "encc": 15, "encod": [9, 15], "encourag": [4, 7, 21, 22, 24], "end": [4, 9, 26], "enemi": 26, "enforc": 9, "engin": [10, 26], "enim": 7, "enjoi": 12, "enough": [9, 16, 21, 22, 24, 26], "ensur": [6, 16], "enter": [12, 15, 21, 22], "entir": [14, 21], "entri": 14, "enumer": 27, "env": [6, 11, 14], "envion": 14, "environ": [7, 10, 12, 15, 19], "episod": [1, 3, 4, 6, 7, 14, 21, 22, 24], "epub": 15, "equal": 24, "equat": 7, "equival": 6, "error": [3, 7, 9, 11, 19, 21, 27], "especi": [9, 26], "ess": 7, "essenti": 6, "est": 7, "estim": 15, "et": [7, 16], "etc": [18, 26], "ethic": 9, "eu": [7, 18], "eupl": [14, 18], "europa": [17, 18], "european": [14, 18], "ev": 9, "evalu": 22, "even": [1, 4, 6, 11, 19, 21, 27], "even_numb": 9, "even_numbers1": 9, "even_numbers2": 9, "event": 15, "event_nam": 7, "everi": [7, 11, 15, 19, 22], "everybodi": 11, "everyon": [2, 23], "everyth": [7, 11, 17], "everywher": 11, "evil": 15, "evolv": 7, "ex": [7, 11], "exact": 15, "exactli": 3, "exam": 10, "exampl": [3, 4, 5, 6, 7, 9, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 25, 26, 27], "excel": 9, "excepteur": 7, "exclude_pattern": 7, "execut": [12, 15, 27], "exercit": 7, "exist": [3, 6, 18, 21, 25, 27], "exit": [8, 15], "expect": [3, 8, 18, 19, 21], "expected_result": 19, "expens": 9, "experi": [7, 14, 19, 25], "experienc": 15, "expert": [4, 27], "explain": [7, 9, 15, 18, 27], "explan": [15, 19, 27], "explor": [7, 12, 22], "export": [6, 14], "express": 17, "extend": [7, 18, 19], "extens": [7, 21, 22], "extern": [6, 7, 9], "extra": [1, 7, 11], "f": [6, 9, 11, 15, 17, 18], "f1000research": 16, "f_to_c_factor": 17, "f_to_c_offset": 17, "face": 6, "fact": 1, "fahrenheit": 19, "fahrenheit_to_celsiu": [17, 19], "fahrenheit_to_celsius_bad": 17, "fail": [3, 19], "fair": 16, "fals": [7, 17, 19], "famili": 16, "familiar": [1, 3, 4, 9, 18], "fast": [6, 15], "faster": [6, 11, 12, 15, 26], "favorit": 10, "favorite_ice_cream": 9, "favourit": [17, 26], "featur": [3, 21, 26], "februari": 25, "feedback": [3, 4, 10, 24], "feel": [7, 19], "fetch": [2, 3, 4, 6], "few": [1, 2, 3, 9, 14, 15, 19, 22, 24, 26, 27], "fewer": [6, 9], "field": 1, "file": [1, 2, 4, 6, 7, 8, 11, 12, 14, 15, 16, 19, 23, 25, 26, 27], "file_path": [9, 15], "filenam": [21, 22], "fill": 18, "final": [7, 8, 14, 16, 21, 27], "find": [7, 15, 18, 21, 22, 24, 27], "findabl": [16, 27], "findal": [9, 15], "fine": 27, "finish": [4, 15, 27], "first": [2, 3, 4, 6, 7, 8, 11, 12, 14, 15, 17, 19, 21, 22, 24, 26, 27], "firstnam": 14, "fix": [1, 3, 4, 9, 18, 19, 26], "flake8": 9, "flask": 26, "flavor": [7, 9], "flexibl": 14, "flit": [6, 11], "flit_cor": 14, "flit_index_url": 14, "flit_password": 14, "flit_usernam": 14, "float": [7, 17], "fly": 6, "focu": 6, "focus": [14, 16], "folder": [12, 22, 27], "follow": [5, 6, 7, 8, 10, 11, 12, 14, 15, 16, 21, 22, 25, 27], "forc": [4, 6, 27], "force11": 16, "force_orphan": 7, "forg": [6, 7, 11, 14, 19], "forget": [4, 7, 24], "forgot": [6, 24], "forgotten": 18, "forgotten_vari": 9, "fork": [7, 10, 20, 24, 25], "form": [4, 16, 21, 24, 26], "format": [9, 16], "formatt": [6, 11, 13], "former": 2, "formerli": 12, "fortran": 6, "found": [9, 11, 25], "fr": 7, "fragment": 1, "free": [6, 7, 9, 11, 12, 26], "freeli": 11, "freez": 6, "frequent": 21, "from": [1, 2, 4, 6, 7, 9, 10, 11, 12, 15, 16, 18, 19, 21, 22, 23, 24, 25, 26, 27], "frustrat": 24, "fugiat": 7, "fulli": [12, 26, 27], "fun": [3, 26], "function": [7, 14, 15, 18, 19, 21, 22], "futur": [4, 6, 8, 12, 18, 26], "g": [2, 6, 7, 9, 12, 15, 17, 18, 21, 22], "gain": 11, "gate": 1, "gener": [2, 6, 8, 9, 11, 12, 15, 18, 19, 21, 26], "generate_data": [8, 14], "generate_predict": [8, 22, 25], "get": [1, 2, 3, 4, 7, 8, 10, 14, 15, 16, 18, 19, 21, 22, 23, 24, 26, 27], "ggplot2": 26, "gh": 7, "gist": 12, "git": [2, 3, 4, 6, 7, 10, 14, 16, 21, 22, 23, 24, 25], "github": [1, 2, 3, 5, 6, 10, 11, 12, 15, 21, 23, 24, 25], "github_token": 7, "githubusercont": [6, 11], "gitignor": [21, 26, 27], "gitlab": [1, 2, 3, 4, 7, 19, 24, 25], "give": [1, 4, 10, 11, 15, 25], "given": [15, 16, 17, 19, 21], "global": [6, 17, 22, 23], "go": [4, 7, 9, 10, 11, 14, 15, 21, 22, 25, 27], "goal": [1, 19], "goe": [18, 21, 22, 24], "gone": [24, 25], "gonzalez": 16, "good": [1, 4, 6, 7, 9, 12, 15, 18, 19, 26, 27], "googl": [12, 25], "gopher": [21, 25], "got": 18, "gpl": 18, "gpu": 15, "grab": 3, "grant": 3, "graph": [21, 25], "graphic": 11, "great": [12, 18, 25, 26], "green": [24, 27], "grep": [17, 22], "group": [1, 4, 10, 16, 18], "grow": [9, 15], "guarante": [16, 19], "guid": [7, 16, 18, 19], "guidelin": 18, "guppi": 15, "gutenberg": 15, "h": 18, "ha": [2, 4, 5, 6, 8, 9, 11, 12, 18, 21, 22, 23, 24, 26], "had": 9, "half": 4, "hand": 10, "handl": 9, "happen": [4, 21, 27], "happi": 23, "hard": [14, 26], "harder": [1, 6], "hash": 21, "have": [1, 2, 4, 6, 7, 9, 10, 12, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 27], "head": 7, "heapi": 15, "heapq": 9, "hear": 4, "help": [4, 6, 8, 9, 15, 19, 23, 26, 27], "helper": 9, "here": [1, 3, 4, 6, 11, 14, 15, 17, 18, 19, 21, 23, 24, 27], "high": [2, 10], "higher": 15, "highlight": [7, 9, 19], "hint": [1, 21, 22, 24], "histor": 22, "histori": [1, 2, 5, 6, 16, 27], "hold": 19, "home": [6, 14], "hong": 16, "hopefulli": [7, 11, 25], "host": [6, 11, 12, 25], "hous": 6, "hover": 22, "how": [0, 2, 8, 9, 10, 14, 15, 18, 20, 21, 23, 24, 25], "howev": [4, 11, 18], "html": [7, 12, 15], "html_theme": 7, "http": [3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 21, 22, 25, 26, 27], "huge": [9, 11, 26], "hugo": 7, "human": 4, "hypothesi": 19, "i": [1, 2, 5, 6, 8, 9, 10, 11, 12, 14, 18, 20, 21, 22, 23, 24, 25], "ic": 9, "icecream": [6, 9, 11], "icon": [4, 7, 11, 21, 27], "id": [7, 12], "idea": [6, 12, 25, 26], "ideal": 4, "identifi": [2, 9, 16, 21, 22, 27], "ignor": [7, 9], "illustr": [21, 22, 24], "imag": [3, 6, 7, 15, 21, 25], "immut": 9, "imperfect": 26, "implement": [1, 2, 9, 11], "import": [6, 8, 9, 10, 11, 12, 15, 17, 18, 21, 22, 24, 26], "imposs": [7, 17], "improv": [1, 6, 7, 9, 10, 15, 21, 26], "impur": 17, "inbox": 4, "inc": 6, "incididunt": 7, "includ": [4, 6, 7], "incompat": 9, "incomplet": 1, "incorpor": 18, "increas": [15, 26], "incredibli": 4, "independ": [2, 7, 9, 10], "index": [7, 14, 22], "indic": [3, 18], "individu": 10, "info": [1, 9], "inform": [3, 4, 9, 12, 15, 16, 18, 21, 22, 24, 26], "informat": 18, "infrequ": 15, "ingredi": 3, "init": [6, 7, 14, 19, 27], "initi": [11, 27], "input": [9, 15, 17], "ins": 18, "insensit": [4, 15, 22], "insid": [6, 9, 17, 22], "insight": [21, 22, 25], "inspect": 22, "inspir": [9, 21, 25, 26], "instal": [4, 7, 9, 10, 15], "instanc": [2, 15, 18, 22], "instead": [1, 4, 6, 7, 19, 22, 27], "institut": [6, 18], "instruct": [3, 4, 6, 7, 10, 21], "instructor": [3, 4, 11, 12], "insur": 18, "int": [9, 15], "integ": 8, "integer_numb": 9, "integr": 7, "intent": 2, "interact": [4, 12], "interchang": [4, 21], "interest": [1, 4, 22, 24], "interf": 25, "interfac": [1, 4, 6, 11, 21, 27], "interfer": [11, 21], "intern": [7, 8], "interpret": 9, "introduc": 1, "introduct": 10, "invent": 7, "invit": 4, "io": [6, 9, 11, 12, 13, 15], "ipsum": 7, "ipynb": 12, "irur": 7, "is_even": 9, "isn": 1, "isol": 11, "isort": [6, 9, 11, 13], "issu": [3, 18, 22, 23, 26], "itertool": 9, "its": [6, 12], "itself": 7, "jane": 16, "januari": 25, "jargon": 11, "jekyl": 7, "job": [7, 14, 19], "john": 7, "join": 19, "joinup": 18, "joss": 7, "journal": [16, 18], "journei": 15, "juli": 18, "julia": [6, 26], "jupinx": 12, "jupit": 16, "jupyt": [9, 11, 12, 16, 21], "jupyterlab": [6, 9, 12, 13], "jupyterlit": 12, "just": [1, 3, 4, 7, 22, 23, 24, 26, 27], "k": 16, "katz": 16, "keep": [1, 3, 7, 15, 17, 18, 23], "kei": [7, 9], "kelvin": 7, "kelvin_to_celsiu": 7, "kept": 7, "keyword": [4, 9, 22], "kib": 27, "kill": 19, "kilomet": 17, "kind": [1, 3, 4, 25], "km": 17, "knitr": 7, "know": [1, 3, 4, 6, 11, 12, 15, 17, 18, 19, 22, 24, 25, 26], "knuth": 15, "l": 27, "l25": 25, "l28": 25, "lab": 11, "label": [8, 24], "labor": 7, "laboratori": 6, "labori": 7, "laborum": 7, "lack": [9, 18], "languag": [6, 9, 10, 11, 17, 18, 19], "laptop": [2, 10, 25], "larg": [9, 15, 27], "larger": [1, 15], "largest": [6, 9], "last": [14, 24], "lastnam": 14, "later": [1, 2, 3, 4, 6, 7, 8, 12, 14, 18, 21, 22, 24, 25, 26], "latest": [2, 6, 25], "latex": [12, 25], "launch": [9, 12], "layout": [23, 26], "lead": 10, "learn": [1, 2, 3, 4, 7, 9, 12, 14, 15, 19, 24, 26], "learner": [4, 7], "least": [1, 7, 18], "leav": [14, 18, 27], "left": [4, 21, 22], "legaci": 14, "legal": 9, "len": [9, 15], "leo": 15, "less": [9, 14, 15, 19], "lesson": [1, 3, 5, 7, 12, 16, 18, 23], "let": [3, 6, 7, 8, 9, 12, 18, 21, 26, 27], "level": [2, 9], "leverag": 7, "librari": [6, 9, 11, 12, 18], "licenc": [14, 18], "licens": [4, 6, 7, 10, 14, 16, 22, 27], "lightweight": 6, "like": [1, 2, 3, 4, 6, 7, 8, 11, 14, 16, 18, 19, 21, 22, 24, 26, 27], "limit": [7, 11], "line": [1, 2, 3, 4, 8, 9, 11, 14, 15, 19, 21, 23, 24, 27], "line_profil": 15, "linear": [8, 22], "link": [3, 4, 7, 9, 15, 18, 27], "linkcheck": 7, "linux": 11, "list": [6, 11, 15, 16, 22, 25], "littl": 24, "live": [7, 12, 23], "ll": [6, 21, 22], "local": [1, 4, 6, 7, 19, 22, 24, 27], "locat": [9, 15, 21], "lock": [6, 9, 18], "log": [9, 15, 16, 21, 22, 24, 27], "login": 27, "long": [6, 9, 22, 23], "longer": [6, 26], "look": [1, 3, 6, 7, 8, 9, 11, 12, 14, 18, 19, 21, 22], "lookup": 15, "lorem": 7, "lose": 26, "lot": [7, 9, 11, 25, 27], "low": 7, "lower": [9, 15], "lunch": 10, "m": [6, 16, 21, 27], "maco": 11, "macosx": 11, "made": [2, 4, 6, 18], "magna": 7, "magnifi": 22, "mai": [7, 18, 21, 26], "main": [1, 2, 3, 6, 7, 9, 11, 14, 15, 19, 22, 23, 24, 26, 27], "mainli": 4, "maintain": [4, 6, 11, 15, 17], "major": [6, 8, 19, 21], "majority_index": [22, 25], "make": [1, 2, 3, 7, 8, 14, 15, 17, 18, 19, 21, 22, 23, 24, 25, 26, 27], "makefil": 6, "mamba": [6, 7, 11, 19], "manag": [4, 9, 11, 18], "mani": [4, 6, 9, 14, 24, 26], "manual": 9, "manuscript": [21, 25], "mar": 27, "mark": [4, 21, 26], "markdown": 12, "marker": 23, "markup": 7, "mashup": 5, "mass": 9, "master": [2, 21, 27], "match": [9, 15, 19, 22], "materi": [3, 5, 16, 18, 19, 22, 24, 25], "math": [7, 17], "mathemat": 17, "matrix": 15, "maxdepth": 7, "maximum": 8, "mayb": [3, 18], "md": [7, 12, 21], "me": [16, 21, 25, 26], "mean": [1, 2, 4, 11, 17, 21, 23, 24, 27], "meaning": 21, "meant": [4, 22, 24], "measur": [7, 23], "mechan": [1, 2], "medic": 15, "member": 9, "memori": [9, 15], "memory_profil": 15, "mention": [4, 6, 18], "mentor": 9, "merg": [1, 2, 3, 4, 10, 20, 21, 22, 23, 26], "messag": [4, 8, 9, 16, 21, 22, 25], "met": 19, "meta": 9, "metadata": [16, 21, 25], "method": [21, 27], "mib": 27, "micromamba": [6, 7, 19], "micropipenv": 6, "microsoft": [9, 12], "middl": 9, "midjournei": 18, "might": [3, 9, 15, 22, 23, 24, 26, 27], "mileston": 26, "min": [7, 10, 19], "mind": [15, 27], "miniatur": 6, "miniconda": [6, 11], "miniforg": 6, "miniforge3": 11, "minim": [6, 7, 11], "minimum": [8, 12], "minor": 3, "mint": 9, "minu": 1, "minut": [9, 12, 15, 22], "miss": 15, "mistak": 1, "mit": 18, "mkdoc": 7, "ml": [6, 11], "model": 12, "modern": [3, 6, 9, 14], "modif": [3, 7, 21], "modifi": [6, 9, 18, 23, 24], "modul": [6, 9, 14, 19], "modular": [10, 23], "mollit": 7, "monopoli": 9, "month": 23, "moon": 17, "more": [1, 6, 11, 19, 21, 22, 23, 25, 26, 27], "most": [4, 6, 8, 9, 10, 14, 15, 18, 21, 22, 24, 26], "motiv": [10, 20, 26], "motto": 14, "mous": 3, "move": [10, 14, 19, 21], "mpi4pi": 9, "mri": 15, "msys2": 6, "much": [1, 3, 4, 6, 7, 9, 10, 11, 15, 19, 20, 21, 22, 25], "multipl": [2, 9, 21, 22, 26], "multiprocess": 9, "music": 18, "must": [4, 18], "mutant": 19, "mutat": 19, "mutmut": 19, "my": [4, 16, 18, 25, 26, 27], "mybind": 12, "myenv": 6, "mypi": 9, "myproject": [25, 27], "myscript": 27, "myst": [6, 11], "myst_enable_extens": 7, "myst_pars": 7, "myvers": 25, "n": [6, 11, 15, 16], "name": [4, 6, 7, 8, 9, 11, 14, 16, 17, 19, 21, 22, 24, 27], "namedtupl": 17, "nation": 6, "navig": [3, 11, 22], "nbconvert": 12, "nbsphinx": 12, "nbviewer": 12, "nc": 18, "nd": 18, "nearest": 10, "necessari": [1, 2, 4, 7, 10, 18, 20, 21], "need": [1, 2, 4, 6, 10, 11, 12, 13, 16, 18, 19, 21, 22, 24, 26, 27], "neg": 7, "neighbor": 10, "neil": 16, "network": [6, 21, 24, 25], "never": [18, 26, 27], "new": [1, 2, 3, 6, 7, 11, 14, 15, 17, 19, 22, 23, 24, 26, 27], "newaddress": 4, "newer": 6, "newfeatur": 25, "next": [1, 6, 9, 14, 19, 21, 22, 23, 24, 27], "nice": [4, 6, 11, 17, 21, 22], "niemey": 16, "nisi": 7, "non": [6, 7, 8, 18], "none": [9, 27], "normal": [8, 21], "nostrud": 7, "note": [3, 4, 12, 15, 21, 24, 26, 27], "notebook": [6, 9, 10, 11, 16, 19, 21], "notedown": 12, "noth": [16, 19], "notic": [3, 24], "notif": 4, "notifi": [4, 15], "notion": 17, "now": [1, 3, 4, 6, 7, 12, 14, 16, 17, 18, 21, 22, 23, 24, 25, 26, 27], "nowadai": 6, "nulla": 7, "num": 8, "num_neighbor": 21, "number": [1, 3, 4, 6, 8, 9, 15, 16, 21], "numer": 8, "numpi": [6, 7, 11, 14, 18, 19, 26], "o": 15, "oauth": 27, "object": 17, "observ": [3, 4, 7], "obtain": 14, "occaecat": 7, "occurr": 22, "odd": 21, "off": [9, 21], "offer": [6, 9, 12, 22, 24, 27], "offic": 18, "officia": 7, "often": [2, 4, 6, 15, 18, 21, 23, 26], "oh": 25, "ok": 24, "old": [6, 14, 19], "older": 27, "onc": [1, 3, 4, 9, 10, 11, 19, 21, 23, 26, 27], "one": [1, 2, 3, 4, 6, 7, 9, 10, 15, 17, 18, 19, 21, 22, 23, 24, 26, 27], "onelin": [21, 22, 24], "ones": 24, "onli": [1, 4, 6, 7, 8, 9, 12, 15, 18, 21, 22, 23, 24, 26, 27], "onlin": 27, "oop": 26, "open": [1, 6, 7, 11, 15, 18, 21, 22, 23, 24, 27], "oper": [2, 6, 8, 9, 11, 15], "opinion": 7, "optim": [9, 17], "option": [6, 8, 10, 11, 20, 21, 22, 24], "orcid": 16, "order": [1, 2, 7, 9], "org": [7, 12, 14, 15, 16, 18, 19], "organ": [4, 9, 14, 22], "orient": 17, "origin": [3, 4, 5, 18, 22, 27], "osi": 14, "other": [0, 1, 2, 4, 6, 7, 8, 9, 10, 11, 14, 18, 21, 22, 23, 24, 25, 26, 27], "otherwis": [1, 15, 26], "our": [1, 4, 7, 8, 12, 14, 16, 18, 19, 21, 22, 24, 25, 27], "ourselv": 15, "out": [1, 3, 7, 13, 15, 18, 21, 22, 25, 26], "output": [7, 8, 9, 22], "outsid": 17, "over": [6, 15, 21, 22, 24, 26], "overhead": 15, "overview": [7, 11, 15, 21, 24], "overwrit": 27, "own": [2, 4, 6, 7, 8, 10, 11, 18, 19, 21, 22, 23, 24, 25, 27], "owner": 18, "p": [16, 18, 26], "pack": 27, "packag": [7, 9, 10, 11, 12, 13, 18], "package_nam": 14, "page": [4, 7, 11, 12, 15, 21, 22, 26, 27], "pai": 3, "paid": 12, "pair": [9, 22], "panda": [6, 7, 11, 14, 18, 19, 26], "paper": [2, 12, 17, 18, 25], "paragraph": 26, "parallel": [10, 17, 25], "paramet": [7, 9], "parent": [2, 11], "pariatur": 7, "parser": [6, 7, 9, 11], "parsl": 9, "part": [1, 4, 7, 9, 12, 14, 15, 16, 18, 19, 21, 23, 24, 27], "particip": [4, 10], "particl": 9, "particle_mass": 9, "particular": [2, 11], "pass": [3, 19], "passeng": 15, "past": [7, 9, 18, 22, 27], "path": [22, 24, 27], "pattern": [21, 22], "pdf": 12, "peac": 15, "peaceiri": 7, "peer": 16, "peerj": 16, "pencil": 21, "peopl": [4, 9, 12, 18, 19, 21, 22, 23, 24, 25, 26], "perfect": 26, "perfetto": 15, "perform": [10, 15], "perhap": 2, "period": 15, "perl": 14, "permalink": 25, "permiss": [7, 14, 18, 24], "persist": [16, 27], "person": [2, 9, 22, 23, 26], "pg2600": 15, "phd": [2, 6, 9], "phone": 15, "pi": 17, "pick": 18, "pictur": [8, 17, 22], "pid": [16, 27], "piec": [4, 22], "pin": 12, "pip": [6, 11, 14], "pipelin": [6, 12], "pipenv": 6, "pixi": 6, "place": [4, 6, 7, 12, 17], "placehold": 27, "plai": 11, "plain": 25, "plan": 23, "planet": 5, "plaqu": 2, "platform": [12, 25], "pleas": [1, 3, 4, 9, 11, 15, 16, 21, 23, 25, 27], "plot": 8, "plot_result": 8, "plu": [1, 27], "plug": 18, "plugin": 18, "poetri": [6, 14], "point": [3, 4, 6, 7, 9, 14, 21, 22, 25, 27], "pointer": 2, "polici": 18, "popular": [4, 7, 9], "portabl": [6, 8, 9], "portion": [18, 23], "posit": 6, "possibl": [1, 3, 4, 6, 9, 10, 14, 16, 18, 21, 22, 24, 25, 26], "possibli": [3, 9, 24, 27], "post": [6, 7, 16, 19], "postdoc": 9, "postpon": 26, "potenti": [9, 15], "power": [1, 14, 27], "practic": [0, 4, 10, 12, 14, 16, 17, 18, 19, 20, 23, 24, 25], "pre": [9, 11, 21], "precis": [4, 6, 15], "predict": [8, 15, 19], "predictor": 10, "prefer": [10, 11, 17, 22], "prematur": 15, "present": [7, 18], "prevent": [16, 27], "preview": [7, 22], "previou": [1, 3, 4, 9, 21, 24, 25], "previous": [4, 7, 21], "primarili": 16, "principl": 16, "print": [2, 7, 11, 15, 17, 21], "prioriti": 9, "privaci": 9, "pro": [6, 11, 15, 17], "probabl": [3, 7, 11, 12, 15, 19, 27], "problem": [1, 3, 4, 9, 15, 18, 25], "process": [1, 9, 15, 19, 21], "processor": [11, 15], "procur": 16, "produc": 8, "product": 1, "profession": 6, "profil": [9, 10, 11], "program": [8, 9, 11, 15, 17, 18, 21, 23], "programm": 15, "progress": [4, 5, 6, 7], "proident": 7, "project": [2, 3, 4, 6, 7, 10, 11, 14, 15, 17, 18, 19, 20, 21, 23, 25], "prompt": 11, "prone": 9, "properti": 19, "propos": [3, 4, 24], "proprietari": [6, 18], "protect": [18, 26], "provid": [6, 7, 9, 11, 15, 16, 18, 22, 24, 26], "public": [2, 12, 14, 16, 18, 24, 27], "publicli": [16, 18, 27], "publish": [4, 6, 10, 12, 14, 18, 23], "publish_branch": 7, "publish_dir": 7, "pull": [2, 19, 23, 25, 26, 27], "pull_request": [7, 19], "purpos": [3, 7, 19, 21], "push": [1, 2, 7, 14, 19, 24, 27], "put": [4, 6, 14, 23], "py": [6, 7, 8, 12, 14, 15, 19, 22, 25, 27], "pyc": 26, "pycodestyl": 9, "pyenv": 6, "pyflak": 9, "pyinstrument": 15, "pylint": 9, "pympler": 15, "pypi": 10, "pypi_token": 14, "pyproject": 14, "pyre": 9, "pyright": 9, "pytest": [6, 11], "python": [5, 8, 9, 12, 15, 17, 19], "q": 10, "qualiti": 1, "quarto": 25, "question": [6, 14, 22, 25], "queue": 9, "qui": 7, "quick": [14, 21], "quickstart": 7, "quirk": 11, "quit": [1, 6, 18, 27], "quot": 15, "quota": 6, "r": [6, 7, 9, 15, 17, 18], "radiu": [8, 17], "rai": 15, "rais": 19, "ram": 6, "ran": 27, "random": 21, "rang": [1, 6, 9], "rare": 23, "rather": [7, 9, 15, 26], "raw": [6, 11], "re": [9, 11, 14, 15, 18], "read": [2, 3, 4, 12, 15, 17, 18], "readabl": 9, "readi": [4, 11, 24], "readm": [12, 21, 27], "readthedoc": [7, 9, 12, 13], "real": [12, 14, 15, 16, 19, 22, 27], "realiz": [6, 18], "realli": [18, 26, 27], "reason": [3, 18, 22, 26], "rebas": 23, "rebuild": 7, "receiv": [4, 9], "recent": [6, 12, 24], "recip": [3, 6, 12], "recipi": 9, "reciproc": 18, "recogn": [1, 4, 9, 16, 25], "recommend": [4, 6, 7, 9, 10, 11, 14, 15, 16, 17, 21, 22, 24], "record": [6, 15, 21], "recov": 26, "recoveri": 27, "recreat": 6, "red": 27, "redirect": [22, 27], "reduc": 23, "ref": [3, 7], "refactor": 10, "refer": [2, 3, 4, 7, 12, 16, 19, 24, 26, 27], "reflect": 18, "refresh": 7, "regard": 7, "regist": 4, "regress": 19, "reject": 3, "rel": 8, "relat": 6, "releas": [2, 7, 10, 14, 16, 18, 19, 21, 26], "relev": [2, 26], "relicens": 18, "reload": 27, "remain": 16, "rememb": [1, 6, 18, 22, 24, 25, 26, 27], "remot": [2, 3, 4, 27], "remov": [6, 9, 18, 23, 24, 27], "renam": 27, "render": [7, 12], "renv": 6, "repair": 26, "repeat": [4, 21], "replac": [3, 5, 6, 14, 21, 27], "repo": [10, 12, 18, 20], "report": [15, 19], "repositori": [0, 1, 5, 6, 7, 10, 12, 14, 16, 18, 21, 25, 26, 27], "reprehenderit": 7, "repres": [15, 18, 27], "reproduc": [5, 7, 11, 18, 25], "request": [2, 9, 18, 19, 23, 25, 26], "requir": [1, 3, 4, 6, 7, 8, 11, 12, 14, 15, 18, 21, 22], "rerun": 12, "research": [1, 4, 6, 11, 16, 18], "resid": 2, "resolut": [10, 20], "resolv": [4, 11], "resourc": 12, "respons": 4, "rest": 9, "restart": 27, "result": [1, 3, 8, 14, 15, 18, 19, 23], "retrac": 14, "return": [7, 9, 15, 17, 19, 24], "reus": [9, 17, 18, 27], "reusabl": [6, 8, 12, 16], "review": [3, 7, 10, 16, 19, 24, 25, 26], "reviv": 19, "rewrit": [16, 18, 19], "right": [3, 4, 7, 9, 19, 21, 22, 24, 27], "risk": [18, 23, 26], "rmd": 7, "room": 18, "root": 15, "row": 8, "rst": 7, "rstudio": [7, 27], "rtd": [6, 7, 11], "ruff": [6, 9, 11], "rule": [6, 9, 18], "run": [3, 6, 7, 9, 11, 12, 14, 15, 16, 19, 22, 27], "rust": 6, "rw": 27, "safe": [24, 26], "safeguard": 26, "sagemathcloud": 12, "sai": [4, 16, 21, 22, 24], "same": [0, 1, 2, 6, 7, 9, 10, 12, 15, 17, 18, 21, 22, 23, 24, 25, 26, 27], "sampl": 8, "sandbox": [12, 16], "satisfi": 18, "save": [7, 8, 9, 11, 15, 26], "saw": [21, 22], "scale": [8, 15], "scalen": [6, 11, 15], "scari": 19, "scatter_plot": 21, "schmitz": 18, "sci": 16, "scicomp": [6, 11, 15], "scienc": 11, "scientif": 11, "scipi": [6, 7, 11, 18, 19, 26], "score": 9, "scratch": 6, "screen": [9, 14, 22], "screenshot": [11, 24, 27], "scriberia": 16, "script": [6, 8, 9, 16, 19, 25], "scroll": [4, 24], "search": [7, 9, 25], "second": [6, 8, 11, 15, 26], "secret": [7, 14], "section": [3, 7, 9, 11, 12, 23], "secur": 9, "sed": 7, "see": [1, 3, 4, 6, 7, 9, 12, 15, 18, 19, 21, 22, 24, 27], "seed": 21, "seem": 21, "seen": [10, 18], "select": [1, 2, 7, 22, 24, 27], "selector": [21, 24], "self": [6, 11, 17, 18], "selfish": 23, "selv": [8, 12, 18], "send": [9, 24, 25, 27], "send_messag": 9, "sens": 21, "sensit": 21, "separ": [6, 11, 19, 21], "sequenc": 15, "server": [2, 7, 9], "servic": [3, 12, 15, 16, 18, 19, 27], "session": [11, 22], "set": [3, 4, 6, 8, 9, 10, 11, 14, 15, 19, 21, 27], "setup": [7, 14, 19, 22, 27], "setuptool": 14, "sever": [11, 21], "sh": [11, 19], "shape": 17, "share": [4, 5, 6, 9, 10, 14, 16, 18, 20, 21, 23, 25], "sharealik": 18, "shell": [6, 7, 11, 17, 19], "short": [3, 15, 21, 27], "shortcut": 22, "shortli": 22, "should": [3, 4, 6, 7, 9, 11, 16, 18, 21, 22, 24, 27], "shouldn": 24, "show": [8, 9, 12, 21, 22, 24], "side": [4, 17, 21], "sidebar": [21, 22, 24], "sign": [1, 21], "signal": 4, "signific": 21, "silli": 3, "similar": [6, 16, 18, 21, 22, 25, 27], "simpl": [8, 9, 14, 16, 17, 19, 22, 26, 27], "simpler": [3, 14], "simplifi": [17, 26], "simul": 15, "sinc": [4, 7, 9, 11, 22, 24, 25, 27], "singl": [1, 21], "singularityc": 6, "sint": 7, "sit": 7, "site": 7, "situat": [2, 6], "size": [8, 26], "skip": [4, 10, 24], "slatkin": 9, "slide": [12, 18], "slightli": 22, "slow": 15, "slower": 17, "slowest": 15, "small": [1, 6, 9, 15, 26, 27], "smaller": [2, 26], "smallest": 9, "smith": 16, "snakemak": [6, 9, 11], "snapshot": [2, 19, 21], "so": [1, 3, 4, 6, 9, 11, 14, 15, 18, 21, 22, 26, 27], "social": [16, 18], "softwar": [3, 6, 7, 15, 25], "solut": [1, 3, 6], "solv": 1, "solver": 6, "some": [1, 3, 4, 6, 7, 8, 9, 10, 12, 15, 18, 21, 22, 24, 25, 26, 27], "some_funct": 15, "somebodi": [1, 3, 4, 6, 17, 18, 19, 23, 25], "somefil": 17, "somehow": 24, "someon": [4, 18, 23, 26], "somestr": 17, "someth": [1, 3, 4, 7, 8, 11, 18, 19, 23, 24, 25, 26], "sometim": [6, 15, 23, 26], "somewhat": 22, "somewher": [18, 25], "soon": 2, "sooner": [6, 15], "sort": [9, 17], "sorted_scor": 9, "sound": 25, "sourc": [1, 3, 5, 6, 7, 9, 11, 21, 22, 24, 25, 27], "space": [8, 21], "spack": 6, "specif": [1, 19, 21], "specifi": [4, 6, 8, 9, 14], "spend": [15, 19], "spent": 15, "spheric": 17, "sphinx": [6, 11, 12], "sphinx_rtd_them": 7, "split": [9, 26], "spy": 15, "ssh": [10, 22, 27], "stabl": 8, "stackoverflow": 18, "stage": 24, "stai": 2, "standard": [6, 9, 15, 18, 22], "start": [3, 4, 6, 10, 14, 15, 21, 22, 23, 24, 25, 26, 27], "stash": 26, "state": [15, 17, 21], "stateless": 17, "static": [12, 18], "station": 15, "statist": 15, "statu": [21, 24], "step": [1, 3, 4, 7, 8, 10, 11, 15, 16, 19, 21, 22, 24, 26, 27], "stick": 21, "sticki": [21, 24], "still": [6, 9, 16, 19, 21, 24, 27], "stop": [15, 21], "storag": 6, "store": [15, 22], "str": [9, 15, 17], "strawberri": 9, "string_numb": 9, "strong": 18, "strongli": 1, "structur": [15, 17, 19], "struggl": 6, "student": [6, 9], "studi": [8, 10], "style": 26, "submit": [2, 3, 4], "submitt": 1, "subscript": 12, "success": [11, 26], "successfulli": 4, "suggest": [2, 3, 4, 18, 25], "suitabl": [4, 25], "summar": 26, "summari": 16, "sunt": 7, "super": 6, "supercomput": [2, 9], "superfund": 6, "support": [6, 9, 12, 16, 18, 21], "sure": [3, 4, 9, 15, 19, 21, 23, 24, 25, 26, 27], "surfac": 17, "surface_area": 17, "surgeri": 15, "surpris": 15, "svg": 8, "switch": [6, 9, 14, 24, 27], "symbol": [1, 22, 27], "symlink": 14, "sync": [3, 6], "syrupi": 19, "system": [6, 8, 11, 14, 25, 27], "systemat": 7, "t": [1, 4, 6, 7, 9, 10, 11, 15, 16, 18, 21, 22, 23, 24, 27], "t_k": 7, "tab": [1, 4, 7, 21, 22], "tag": [2, 26], "take": [2, 3, 6, 8, 12, 17, 22, 27], "target": 24, "task": [1, 3, 4, 5, 7, 9, 10, 19, 21, 22, 25], "teach": 9, "team": [2, 4], "technic": [1, 3, 4, 7, 23], "techniqu": [8, 19], "tell": [3, 19, 23], "temp_c": [7, 17, 19], "temp_f": [17, 19], "temp_k": 7, "temperatur": [7, 19], "templat": 26, "tempor": 7, "temporari": 21, "tend": 9, "term": [6, 18, 22], "termin": [10, 11, 21, 22, 27], "terminologi": 18, "test": [8, 9, 10, 13, 17, 21, 25], "test_": 19, "test_add": 19, "test_fahrenheit_to_celsiu": 19, "text": [7, 8, 10, 15, 23, 25], "than": [6, 9, 15, 22, 25, 26, 27], "thank": 4, "thei": [4, 6, 9, 11, 15, 17, 18, 21, 22, 24, 25, 26], "them": [1, 3, 4, 6, 10, 12, 15, 18, 19, 21, 24, 25], "theme": [6, 7, 11], "themselv": [2, 24], "therefor": 10, "thesi": 25, "thi": [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 21, 23, 24, 25, 26, 27], "thing": [4, 6, 9, 11, 14, 17, 21, 22, 23, 24, 26], "think": [4, 18, 23, 24], "those": [24, 27], "though": 11, "thought": 19, "thousand": 9, "thread": 27, "three": [7, 9, 13, 21, 22, 24, 27], "threshold": 7, "through": [1, 3, 4, 10, 11, 15, 16], "thu": [11, 24], "thumb": 7, "tile": 11, "time": [2, 3, 6, 7, 9, 11, 12, 15, 18, 19, 21, 22, 23, 24, 25, 27], "timelin": 22, "tini": [1, 6, 7], "titl": [4, 16, 24], "toctre": 7, "togeth": [1, 2, 8, 10, 11, 13, 14, 21, 22, 24, 26], "token": 14, "told": 17, "tolstoi": 15, "toml": [6, 9, 14], "tomorrow": 24, "too": [7, 9, 21, 22, 26], "took": 15, "tool": [7, 10, 12, 14, 22, 25, 27], "top": [4, 7, 8, 17, 21, 22, 24, 27], "topic": 11, "total": 27, "toward": [1, 19, 22, 24], "trace": 6, "tracemalloc": 15, "track": [4, 15, 21, 22, 26, 27], "tradition": 6, "train": [8, 15], "translat": 19, "travel": 6, "travers": [15, 19], "tri": [6, 25, 27], "trick": 21, "tricki": 18, "troubleshoot": [4, 27], "true": [7, 17, 19], "truli": 27, "trust": 22, "try": [3, 4, 6, 7, 8, 9, 10, 12, 15, 16, 19, 21, 22, 23, 24, 25, 27], "tune": 2, "tupl": [9, 17], "ture": 16, "turn": [10, 20, 21, 25, 26], "tutori": [7, 17, 21, 24], "twine": 14, "two": [1, 3, 7, 8, 10, 12, 18, 22, 23, 24, 25, 27], "txt": [6, 9, 11, 12, 14, 15], "type": [4, 10, 11, 14, 16, 21, 22, 24], "typic": [2, 9, 10, 18, 26], "typo": 1, "typographi": 7, "u": [4, 6, 8, 9, 15, 18, 21, 22, 24, 27], "ubuntu": [7, 14, 19], "ugli": 26, "ui": 6, "uit": 18, "ullamco": 7, "un": 24, "uncom": 14, "under": [2, 18, 22, 27], "understand": [1, 6, 8, 9, 15, 17, 18, 21, 22, 23, 24], "understood": [12, 26], "undo": 26, "unfinish": [26, 27], "unfortun": [11, 14, 22], "union": [14, 18], "uniq": 17, "uniqu": [2, 9, 15, 21, 22], "unique_word": [9, 15], "unit": 26, "univers": [6, 18, 25], "unix": 17, "unless": [6, 11, 18, 27], "unmaintain": 6, "unrel": [9, 23, 26], "unsur": [3, 11, 19, 21, 26], "untest": 26, "until": 4, "unus": 9, "unwatch": 4, "up": [3, 4, 6, 10, 11, 14, 15, 17, 23, 24, 26, 27], "updat": [1, 2, 11, 18, 26], "upload": [14, 27], "upper": 21, "uproar": 6, "upstream": [4, 22, 23, 24], "url": [3, 4, 12, 14, 21, 22, 27], "us": [1, 2, 3, 4, 5, 6, 7, 15, 16, 18, 19, 21, 24, 25, 27], "usabl": 11, "usag": [8, 14, 15, 21, 22], "user": [3, 4, 11, 18, 21, 22, 24, 27], "usernam": [3, 4, 12, 21, 22], "usual": 22, "ut": 7, "utf": [9, 15], "uv": 6, "v": [4, 10, 21, 24, 27], "v1": [7, 19, 21], "v4": [7, 14, 19], "v5": 14, "valid": 16, "valu": [4, 9, 16, 17], "vanilla": 9, "variabl": [9, 22], "variou": 18, "ve": 4, "velit": 7, "veniam": 7, "venu": 16, "venv": 6, "verbos": [3, 4, 9], "veri": [1, 3, 4, 7, 11, 14, 19, 21, 22, 26, 27], "verifi": [3, 4, 27], "versatil": 14, "version": [2, 7, 9, 10, 12, 14, 16, 19, 21, 22, 24, 27], "version1": 21, "version2": 21, "via": [4, 12, 21, 27], "video": 16, "view": [1, 3, 4, 7, 12, 15, 21, 22, 24], "violat": 9, "virtual": 6, "virtualenv": 6, "visibl": [4, 22], "visit": [7, 12], "visual": 12, "vl": [6, 7, 11, 19], "voil\u00e0": 12, "vol": 16, "volupt": 7, "vote": [19, 21], "w": [7, 9, 15], "wa": [3, 4, 6, 7, 9, 17, 21, 22, 25, 26], "wai": [1, 3, 4, 6, 7, 9, 11, 14, 16, 17, 19, 21, 22, 23, 24, 26], "walk": [3, 4], "want": [1, 3, 4, 7, 11, 14, 16, 18, 19, 21, 22, 24, 25, 27], "war": 15, "warm": 7, "warn": 9, "watch": [4, 23], "we": [1, 2, 3, 4, 6, 7, 9, 10, 12, 13, 14, 16, 18, 19, 21, 22, 23, 24, 26, 27], "web": [1, 4, 6, 7, 11, 16, 25, 27], "websit": [7, 18, 25, 27], "welcom": 10, "well": 17, "were": [4, 24], "what": [1, 3, 6, 8, 9, 11, 15, 16, 17, 21, 22, 23, 24, 27], "whatthecommit": 26, "wheel": 7, "when": [2, 3, 4, 6, 7, 9, 14, 15, 18, 19, 21, 23, 24, 25, 26], "where": [2, 3, 4, 6, 9, 10, 15, 16, 18, 21, 22, 23, 24, 25, 26], "wherea": 2, "wherev": 11, "whether": [2, 3, 8, 9, 15, 18, 19, 24], "which": [1, 2, 3, 4, 6, 7, 10, 11, 12, 14, 15, 16, 17, 18, 19, 21, 23, 24, 25, 26, 27], "while": 6, "who": [3, 4, 12, 18], "whole": [1, 2, 15, 19, 22], "why": [3, 6, 9, 14, 16, 26], "willing": 18, "window": [4, 11, 22], "wish": [3, 4, 11, 15, 17], "within": [0, 2, 10, 24], "without": [1, 3, 4, 6, 7, 9, 11, 15, 18, 22, 24, 25, 27], "wonder": 3, "word": [9, 15, 18, 19, 24, 25], "work": [1, 2, 3, 4, 7, 8, 9, 10, 12, 14, 16, 19, 21, 23, 24, 25, 27], "workflow": [3, 7, 9, 10, 14, 19], "workshop": [3, 4, 5, 10, 15, 19, 22, 24, 25], "world": [15, 19], "worst": 18, "worth": 15, "would": [2, 3, 14, 16, 17, 18, 19, 22, 24, 26], "wrap": 9, "write": [2, 3, 4, 6, 7, 12, 14, 18, 19, 21, 27], "written": 8, "wrong": [4, 9, 15], "wrote": 10, "www": 15, "x": [9, 15, 17], "x86_64": 11, "xkcd": 6, "xy": 17, "y": [9, 17], "yaml": [6, 9], "yapf": 9, "yappi": 15, "ye": [14, 22, 27], "year": [16, 18, 26], "yellow": 24, "yet": [4, 19, 21, 24, 27], "yml": [6, 7, 11, 12, 14, 19], "you": [1, 2, 3, 4, 6, 10, 12, 15, 17, 18, 19, 23, 24, 25, 26, 27], "your": [1, 2, 4, 6, 8, 10, 14, 15, 18, 19, 20, 21, 22, 23, 26], "yourself": [18, 27], "z": 9, "zenodo": [12, 16, 18, 27], "zip": 25, "zola": 7, "zombi": 7}, "titles": ["Collaborative version control and code review", "Practicing code review", "Concepts around collaboration", "How to contribute changes to repositories that belong to others", "Collaborating within the same repository", "Credit", "Reproducible environments and dependencies", "Where to start with documentation", "Example project: 2D classification task using a nearest-neighbor predictor", "Tools and useful practices", "Reproducible research software development using Python (ML example)", "Software install instructions", "Sharing notebooks", "Other useful tooling for notebooks", "Creating a Python package and deploying it to PyPI", "Profiling", "How to publish your code", "Concepts in refactoring and modular code design", "Choosing a software license", "Automated testing", "Introduction to version control with Git and GitHub", "Creating branches and commits", "Forking, cloning, and browsing", "Conflict resolution", "Merging changes and contributing to the project", "Motivation", "Practical advice: How much Git is necessary?", "Optional: How to turn your project to a Git repo and share it"], "titleterms": {"": 3, "1": [4, 14, 21, 22, 24], "15": 12, "2": [4, 14, 21, 22, 24], "20": [21, 22, 27], "25": [1, 3, 4], "2d": 8, "3": [4, 14, 21, 22, 24], "4": [4, 21, 22, 24], "5": [4, 21, 22, 24], "6": [4, 21, 22, 24], "7": [4, 21, 24], "8": [9, 21], "And": 4, "But": 17, "If": 11, "In": 7, "It": 6, "One": 15, "The": [6, 23], "There": 11, "about": [7, 25, 26], "action": [7, 14, 19], "activ": 11, "ad": [4, 19], "addit": 15, "address": 1, "advic": 26, "again": 21, "ai": 9, "all": [9, 26], "allow": [7, 22], "an": [4, 9, 11, 14, 22], "anaconda": 11, "analogi": 15, "annot": 9, "anyon": 12, "api": 7, "approach": 3, "ar": [9, 11, 17, 22], "arbitrari": 21, "around": 2, "ask": 1, "assist": 9, "auto": [7, 9], "autom": [3, 19], "avoid": [6, 9, 23, 26], "back": 25, "background": 21, "basic": 22, "befor": 17, "begin": 24, "belong": 3, "best": 6, "between": 2, "beyond": 9, "big": 10, "binder": 12, "box": 9, "branch": [2, 4, 21, 24, 25, 26], "brows": [21, 22], "build": 7, "can": [3, 9, 22], "catch": 9, "chang": [1, 2, 3, 4, 24], "check": 9, "checklist": 7, "choic": 11, "choos": [11, 15, 18], "citabl": 16, "cite": 16, "class": 17, "classif": 8, "clone": [2, 22], "code": [0, 1, 7, 9, 13, 15, 16, 17, 22, 25], "collabor": [0, 2, 3, 4, 17, 25], "collect": 9, "command": [22, 26], "comment": 1, "commit": [2, 21, 22, 26], "commun": 6, "compar": [21, 22], "complex": 26, "comprehens": 9, "compromis": 11, "concept": [2, 17], "conda": [6, 11], "configur": 9, "conflict": 23, "confus": 7, "consid": 9, "constitut": 18, "contain": 6, "contribut": [3, 24], "control": [0, 20], "copi": 22, "copyright": 18, "coupl": 3, "creat": [4, 14, 21, 22, 24], "credit": 5, "cross": 4, "current": 16, "databas": 17, "debug": 9, "delet": 24, "demo": [7, 12, 14, 16, 18], "demonstr": [23, 25], "depend": [6, 12], "deploi": 14, "deriv": 18, "design": 17, "develop": 10, "differ": 12, "digit": 12, "discuss": [1, 3, 8, 15, 16, 18, 25], "disk": 17, "distribut": 6, "di\u00e1taxi": 7, "do": 25, "docstr": 7, "document": [7, 12, 17], "doi": 12, "don": 3, "draft": 4, "dynam": 12, "each": 26, "ecosystem": 6, "els": 19, "end": 19, "enough": 7, "enumer": 9, "environ": [6, 11], "ergonom": 9, "etc": 11, "even": 15, "exampl": [8, 10], "exercis": [1, 3, 4, 7, 12, 15, 16, 18, 19, 21, 22, 24, 27], "exist": [15, 22], "explain": [6, 16], "featur": 25, "file": [9, 21, 22], "fill": 24, "filter": 9, "flit": 14, "focu": [8, 16], "follow": 9, "fork": [2, 3, 22], "format": 13, "formatt": 9, "from": [3, 14, 17], "function": [9, 17], "futur": 7, "gener": 7, "get": 12, "git": [20, 26, 27], "github": [4, 7, 14, 16, 19, 20, 22, 27], "gitlab": [16, 27], "goal": [8, 10], "good": 11, "graph": 22, "great": 16, "guess": 15, "guid": 9, "have": [3, 11, 14], "help": [1, 3, 14], "here": 9, "hint": [4, 9], "histori": 22, "host": 7, "how": [1, 3, 4, 6, 7, 11, 12, 16, 17, 19, 22, 26, 27], "human": 23, "i": [3, 4, 7, 15, 16, 17, 19, 26, 27], "idea": 3, "identifi": 12, "imagin": 15, "includ": 22, "index": [6, 9], "indic": 9, "initi": 14, "insert": 15, "instal": [6, 11, 14], "instantan": 22, "instead": [9, 15], "instruct": 11, "instructor": [7, 8, 13, 19, 22], "introduct": 20, "isol": 6, "issu": 4, "iter": 9, "jupyterlab": 11, "just": [14, 21], "keep": [11, 25], "keypoint": [6, 7], "know": 9, "languag": 7, "larg": 26, "last": 22, "learn": 8, "level": 26, "licens": 18, "like": [9, 25], "line": [22, 26], "lint": 9, "linter": 9, "list": 9, "local": [3, 14], "london": 15, "loop": 9, "machin": 6, "made": 21, "main": [4, 21], "make": [4, 9, 12, 16], "manag": 6, "mani": [7, 11, 15], "map": 9, "markdown": 7, "matter": 18, "measur": 15, "merg": [24, 25], "messag": 26, "metadata": 14, "method": 11, "min": [1, 3, 4, 12, 21, 22, 27], "miniforg": 11, "ml": 10, "modif": 22, "modifi": [1, 4, 21, 22], "modular": 17, "mondai": 10, "more": [7, 9, 14, 16, 18], "motiv": [3, 19, 25], "much": 26, "my": [3, 6], "myst": 7, "navig": 24, "nearest": 8, "necessari": 26, "need": [7, 9, 25], "neighbor": 8, "network": [17, 22], "new": [4, 21], "note": [7, 8, 13, 19, 22], "notebook": [12, 13], "o": [9, 17], "object": [2, 6, 7, 9, 12, 14, 15, 16, 18, 19, 21, 22, 24, 25, 27], "often": [7, 9, 19], "one": 14, "open": [3, 4, 9], "optim": 15, "option": [7, 27], "origin": 24, "other": [3, 13], "our": 9, "out": 24, "over": 9, "packag": [6, 14], "paper": 16, "parallel": 9, "part": 6, "particular": 22, "path": 9, "pathlib": 9, "peopl": 3, "pep": 9, "permiss": 3, "pictur": 10, "pin": 6, "possibl": [7, 19], "practic": [1, 6, 9, 15, 21, 26], "predictor": 8, "prefer": 9, "prepar": [1, 3, 4, 7, 10, 22], "print": 9, "process": 24, "profil": 15, "project": [8, 9, 22, 24, 26, 27], "protect": 4, "public": 6, "publish": [16, 27], "pull": [1, 3, 4, 24], "pure": 17, "push": [3, 4], "put": [16, 27], "pypi": [6, 14], "pytest": 19, "python": [6, 7, 10, 11, 14], "question": [9, 17], "read": [7, 9], "readm": 7, "recip": 4, "recoveri": 3, "refactor": 17, "referenc": 4, "rel": 9, "remix": 18, "remov": 11, "renam": 21, "repo": 27, "report": 6, "repositori": [2, 3, 4, 22, 24], "reproduc": [6, 10, 12], "request": [1, 3, 4, 24], "research": 10, "resolut": 23, "resolv": 23, "resourc": [15, 16, 18], "restructuredtext": 7, "review": [0, 1, 4], "right": 15, "roll": 25, "run": 8, "same": [3, 4], "sampl": [15, 18], "schedul": 10, "scientif": 16, "scope": 9, "script": 14, "search": 22, "self": 7, "set": 7, "share": [12, 22, 27], "should": [15, 26], "side": 23, "simplest": 15, "singl": 22, "situat": 18, "size": 15, "slice": 9, "snapshot": 25, "softwar": [10, 11, 16, 18, 27], "solut": [4, 15, 18, 21, 22, 24, 25], "sphinx": 7, "stage": 26, "start": [7, 11, 17, 19], "statement": 9, "static": 9, "statu": 26, "step": 14, "structur": 9, "style": 9, "subprocess": 9, "subwai": 15, "suggest": [1, 11], "summari": [1, 3, 4, 21, 22, 24], "supercomput": 6, "switch": 21, "synchron": 2, "system": [9, 15], "t": 3, "tag": 21, "talk": 25, "task": 8, "taught": 19, "taxonomi": 18, "test": [3, 14, 19], "than": [7, 14], "thesi": 6, "thi": [14, 22], "through": [21, 22, 24], "thursdai": 10, "time": 26, "timer": 15, "tool": [6, 9, 11, 13, 15], "toward": [3, 4], "trace": 15, "track": 25, "train": 22, "try": [11, 14], "tuesdai": 10, "turn": 27, "two": [6, 9, 21], "type": 9, "typic": 25, "underground": 15, "unit": 19, "unpack": 9, "up": [7, 9], "updat": [3, 6], "upstream": 3, "us": [8, 9, 10, 11, 12, 13, 14, 22, 26], "v": [7, 15, 22], "verifi": [11, 24], "version": [0, 6, 20, 25], "via": 11, "wai": 12, "walk": [21, 22, 24], "want": 15, "we": [8, 11, 15, 25], "wednesdai": 10, "what": [4, 7, 14, 18, 19, 25, 26], "when": 22, "where": [7, 19], "which": [9, 22], "who": 22, "why": [7, 18, 25], "within": [3, 4], "word": 22, "work": [6, 11, 18, 22, 26], "write": [9, 17, 26], "you": [7, 9, 11, 14, 16, 21, 22], "your": [3, 7, 9, 11, 12, 16, 17, 24, 27], "yourself": 22, "zip": 9}})
\ No newline at end of file
diff --git a/singlehtml/_images/8-fair-principles.jpg b/singlehtml/_images/8-fair-principles.jpg
new file mode 100644
index 0000000..efdfddb
Binary files /dev/null and b/singlehtml/_images/8-fair-principles.jpg differ
diff --git a/singlehtml/_images/add-suggestion.png b/singlehtml/_images/add-suggestion.png
new file mode 100644
index 0000000..0a28544
Binary files /dev/null and b/singlehtml/_images/add-suggestion.png differ
diff --git a/singlehtml/_images/all-checks-failed.png b/singlehtml/_images/all-checks-failed.png
new file mode 100644
index 0000000..5175684
Binary files /dev/null and b/singlehtml/_images/all-checks-failed.png differ
diff --git a/singlehtml/_images/bare-repository.png b/singlehtml/_images/bare-repository.png
new file mode 100644
index 0000000..45286f9
Binary files /dev/null and b/singlehtml/_images/bare-repository.png differ
diff --git a/singlehtml/_images/binder.jpg b/singlehtml/_images/binder.jpg
new file mode 100644
index 0000000..d04c467
Binary files /dev/null and b/singlehtml/_images/binder.jpg differ
diff --git a/singlehtml/_images/branches.png b/singlehtml/_images/branches.png
new file mode 100644
index 0000000..2464b1c
Binary files /dev/null and b/singlehtml/_images/branches.png differ
diff --git a/singlehtml/_images/button.jpg b/singlehtml/_images/button.jpg
new file mode 100644
index 0000000..4a0d99e
Binary files /dev/null and b/singlehtml/_images/button.jpg differ
diff --git a/singlehtml/_images/chart.svg b/singlehtml/_images/chart.svg
new file mode 100644
index 0000000..23cb3e9
--- /dev/null
+++ b/singlehtml/_images/chart.svg
@@ -0,0 +1 @@
+label 0 1 −12 −10 −8 −6 −4 −2 0 2 4 6 8 10 12 x −12 −10 −8 −6 −4 −2 0 2 4 6 8 10 12 x false true correct Are predictions correct? (accuracy: 0.94) label 0 1 −12 −10 −8 −6 −4 −2 0 2 4 6 8 10 12 x −12 −10 −8 −6 −4 −2 0 2 4 6 8 10 12 x 0 1 label Training data
\ No newline at end of file
diff --git a/singlehtml/_images/chatgpt.png b/singlehtml/_images/chatgpt.png
new file mode 100644
index 0000000..6794801
Binary files /dev/null and b/singlehtml/_images/chatgpt.png differ
diff --git a/singlehtml/_images/check-details.png b/singlehtml/_images/check-details.png
new file mode 100644
index 0000000..456a247
Binary files /dev/null and b/singlehtml/_images/check-details.png differ
diff --git a/singlehtml/_images/clone-address.png b/singlehtml/_images/clone-address.png
new file mode 100644
index 0000000..abdbd85
Binary files /dev/null and b/singlehtml/_images/clone-address.png differ
diff --git a/singlehtml/_images/clone-of-fork.png b/singlehtml/_images/clone-of-fork.png
new file mode 100644
index 0000000..e610a1a
Binary files /dev/null and b/singlehtml/_images/clone-of-fork.png differ
diff --git a/singlehtml/_images/clone.png b/singlehtml/_images/clone.png
new file mode 100644
index 0000000..9e79c46
Binary files /dev/null and b/singlehtml/_images/clone.png differ
diff --git a/singlehtml/_images/code-completion.gif b/singlehtml/_images/code-completion.gif
new file mode 100644
index 0000000..e0bed42
Binary files /dev/null and b/singlehtml/_images/code-completion.gif differ
diff --git a/singlehtml/_images/comment.png b/singlehtml/_images/comment.png
new file mode 100644
index 0000000..6ae948c
Binary files /dev/null and b/singlehtml/_images/comment.png differ
diff --git a/singlehtml/_images/commit-suggestion.png b/singlehtml/_images/commit-suggestion.png
new file mode 100644
index 0000000..4ec4bb6
Binary files /dev/null and b/singlehtml/_images/commit-suggestion.png differ
diff --git a/singlehtml/_images/create-repository-with-readme.png b/singlehtml/_images/create-repository-with-readme.png
new file mode 100644
index 0000000..2411834
Binary files /dev/null and b/singlehtml/_images/create-repository-with-readme.png differ
diff --git a/singlehtml/_images/create-repository.png b/singlehtml/_images/create-repository.png
new file mode 100644
index 0000000..80fb99c
Binary files /dev/null and b/singlehtml/_images/create-repository.png differ
diff --git a/singlehtml/_images/draft-pr-wip.png b/singlehtml/_images/draft-pr-wip.png
new file mode 100644
index 0000000..12181d5
Binary files /dev/null and b/singlehtml/_images/draft-pr-wip.png differ
diff --git a/singlehtml/_images/draft-pr.png b/singlehtml/_images/draft-pr.png
new file mode 100644
index 0000000..2099349
Binary files /dev/null and b/singlehtml/_images/draft-pr.png differ
diff --git a/singlehtml/_images/exercise.png b/singlehtml/_images/exercise.png
new file mode 100644
index 0000000..ddfe435
Binary files /dev/null and b/singlehtml/_images/exercise.png differ
diff --git a/singlehtml/_images/files-changed.png b/singlehtml/_images/files-changed.png
new file mode 100644
index 0000000..7b58a22
Binary files /dev/null and b/singlehtml/_images/files-changed.png differ
diff --git a/singlehtml/_images/fork-after-update.png b/singlehtml/_images/fork-after-update.png
new file mode 100644
index 0000000..38fc8c6
Binary files /dev/null and b/singlehtml/_images/fork-after-update.png differ
diff --git a/singlehtml/_images/fork.png b/singlehtml/_images/fork.png
new file mode 100644
index 0000000..50cd9cc
Binary files /dev/null and b/singlehtml/_images/fork.png differ
diff --git a/singlehtml/_images/forkandclone.png b/singlehtml/_images/forkandclone.png
new file mode 100644
index 0000000..d050183
Binary files /dev/null and b/singlehtml/_images/forkandclone.png differ
diff --git a/singlehtml/_images/github-branches.png b/singlehtml/_images/github-branches.png
new file mode 100644
index 0000000..111eca1
Binary files /dev/null and b/singlehtml/_images/github-branches.png differ
diff --git a/singlehtml/_images/github-compare-and-pr.png b/singlehtml/_images/github-compare-and-pr.png
new file mode 100644
index 0000000..efca375
Binary files /dev/null and b/singlehtml/_images/github-compare-and-pr.png differ
diff --git a/singlehtml/_images/github-comparing-changes.png b/singlehtml/_images/github-comparing-changes.png
new file mode 100644
index 0000000..3f6f718
Binary files /dev/null and b/singlehtml/_images/github-comparing-changes.png differ
diff --git a/singlehtml/_images/github-contribute.png b/singlehtml/_images/github-contribute.png
new file mode 100644
index 0000000..ef80527
Binary files /dev/null and b/singlehtml/_images/github-contribute.png differ
diff --git a/singlehtml/_images/github-merged.png b/singlehtml/_images/github-merged.png
new file mode 100644
index 0000000..fcd4404
Binary files /dev/null and b/singlehtml/_images/github-merged.png differ
diff --git a/singlehtml/_images/github-navigate-to-branch.png b/singlehtml/_images/github-navigate-to-branch.png
new file mode 100644
index 0000000..9ac4731
Binary files /dev/null and b/singlehtml/_images/github-navigate-to-branch.png differ
diff --git a/singlehtml/_images/good-vs-bad.svg b/singlehtml/_images/good-vs-bad.svg
new file mode 100644
index 0000000..e787bea
--- /dev/null
+++ b/singlehtml/_images/good-vs-bad.svg
@@ -0,0 +1,191 @@
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+ impure
+ bad code
+
+
+ impure
+ better code
+ pure
+
+
+ impure
+ good code
+ pure
+
+
+
diff --git a/singlehtml/_images/gophers.png b/singlehtml/_images/gophers.png
new file mode 100644
index 0000000..741406b
Binary files /dev/null and b/singlehtml/_images/gophers.png differ
diff --git a/singlehtml/_images/history.png b/singlehtml/_images/history.png
new file mode 100644
index 0000000..faecc66
Binary files /dev/null and b/singlehtml/_images/history.png differ
diff --git a/singlehtml/_images/issue-number.png b/singlehtml/_images/issue-number.png
new file mode 100644
index 0000000..be273a2
Binary files /dev/null and b/singlehtml/_images/issue-number.png differ
diff --git a/singlehtml/_images/leave-comment.png b/singlehtml/_images/leave-comment.png
new file mode 100644
index 0000000..95e33c2
Binary files /dev/null and b/singlehtml/_images/leave-comment.png differ
diff --git a/singlehtml/_images/license-models.png b/singlehtml/_images/license-models.png
new file mode 100644
index 0000000..f413617
Binary files /dev/null and b/singlehtml/_images/license-models.png differ
diff --git a/singlehtml/_images/license.png b/singlehtml/_images/license.png
new file mode 100644
index 0000000..ec5cf85
Binary files /dev/null and b/singlehtml/_images/license.png differ
diff --git a/singlehtml/_images/merging.png b/singlehtml/_images/merging.png
new file mode 100644
index 0000000..b954afb
Binary files /dev/null and b/singlehtml/_images/merging.png differ
diff --git a/singlehtml/_images/network.png b/singlehtml/_images/network.png
new file mode 100644
index 0000000..96b97ae
Binary files /dev/null and b/singlehtml/_images/network.png differ
diff --git a/singlehtml/_images/new-repository.png b/singlehtml/_images/new-repository.png
new file mode 100644
index 0000000..aff7677
Binary files /dev/null and b/singlehtml/_images/new-repository.png differ
diff --git a/singlehtml/_images/owl.png b/singlehtml/_images/owl.png
new file mode 100644
index 0000000..7f90e9f
Binary files /dev/null and b/singlehtml/_images/owl.png differ
diff --git a/singlehtml/_images/packages.jpg b/singlehtml/_images/packages.jpg
new file mode 100644
index 0000000..421b4fc
Binary files /dev/null and b/singlehtml/_images/packages.jpg differ
diff --git a/singlehtml/_images/pull-request-form.png b/singlehtml/_images/pull-request-form.png
new file mode 100644
index 0000000..c6c6af8
Binary files /dev/null and b/singlehtml/_images/pull-request-form.png differ
diff --git a/singlehtml/_images/record-player.png b/singlehtml/_images/record-player.png
new file mode 100644
index 0000000..467c838
Binary files /dev/null and b/singlehtml/_images/record-player.png differ
diff --git a/singlehtml/_images/sharing.png b/singlehtml/_images/sharing.png
new file mode 100644
index 0000000..8368d6b
Binary files /dev/null and b/singlehtml/_images/sharing.png differ
diff --git a/singlehtml/_images/sync-fork.png b/singlehtml/_images/sync-fork.png
new file mode 100644
index 0000000..848a054
Binary files /dev/null and b/singlehtml/_images/sync-fork.png differ
diff --git a/singlehtml/_images/testing-jupyter1.png b/singlehtml/_images/testing-jupyter1.png
new file mode 100644
index 0000000..38bd6cc
Binary files /dev/null and b/singlehtml/_images/testing-jupyter1.png differ
diff --git a/singlehtml/_images/testing-jupyter2.png b/singlehtml/_images/testing-jupyter2.png
new file mode 100644
index 0000000..53c541e
Binary files /dev/null and b/singlehtml/_images/testing-jupyter2.png differ
diff --git a/singlehtml/_images/testing-jupyter3.png b/singlehtml/_images/testing-jupyter3.png
new file mode 100644
index 0000000..1b1e47a
Binary files /dev/null and b/singlehtml/_images/testing-jupyter3.png differ
diff --git a/singlehtml/_images/testing-jupyter4.png b/singlehtml/_images/testing-jupyter4.png
new file mode 100644
index 0000000..25c7795
Binary files /dev/null and b/singlehtml/_images/testing-jupyter4.png differ
diff --git a/singlehtml/_images/testing-jupyter5.png b/singlehtml/_images/testing-jupyter5.png
new file mode 100644
index 0000000..c8d3491
Binary files /dev/null and b/singlehtml/_images/testing-jupyter5.png differ
diff --git a/singlehtml/_images/turntable.png b/singlehtml/_images/turntable.png
new file mode 100644
index 0000000..38829e7
Binary files /dev/null and b/singlehtml/_images/turntable.png differ
diff --git a/singlehtml/_images/unwatch.png b/singlehtml/_images/unwatch.png
new file mode 100644
index 0000000..020a6c9
Binary files /dev/null and b/singlehtml/_images/unwatch.png differ
diff --git a/singlehtml/_images/upload-files.png b/singlehtml/_images/upload-files.png
new file mode 100644
index 0000000..fd3ef8c
Binary files /dev/null and b/singlehtml/_images/upload-files.png differ
diff --git a/singlehtml/_images/vscode-authorize.png b/singlehtml/_images/vscode-authorize.png
new file mode 100644
index 0000000..4d45dc2
Binary files /dev/null and b/singlehtml/_images/vscode-authorize.png differ
diff --git a/singlehtml/_images/vscode-publish-branch.png b/singlehtml/_images/vscode-publish-branch.png
new file mode 100644
index 0000000..323ffb2
Binary files /dev/null and b/singlehtml/_images/vscode-publish-branch.png differ
diff --git a/singlehtml/_images/vscode-publish-to-github1.png b/singlehtml/_images/vscode-publish-to-github1.png
new file mode 100644
index 0000000..883e756
Binary files /dev/null and b/singlehtml/_images/vscode-publish-to-github1.png differ
diff --git a/singlehtml/_images/vscode-publish-to-github2.png b/singlehtml/_images/vscode-publish-to-github2.png
new file mode 100644
index 0000000..d0055db
Binary files /dev/null and b/singlehtml/_images/vscode-publish-to-github2.png differ
diff --git a/singlehtml/_images/vscode-publish-to-github3.png b/singlehtml/_images/vscode-publish-to-github3.png
new file mode 100644
index 0000000..0f68d0e
Binary files /dev/null and b/singlehtml/_images/vscode-publish-to-github3.png differ
diff --git a/singlehtml/_images/vscode-start.png b/singlehtml/_images/vscode-start.png
new file mode 100644
index 0000000..e783080
Binary files /dev/null and b/singlehtml/_images/vscode-start.png differ
diff --git a/singlehtml/_static/_sphinx_javascript_frameworks_compat.js b/singlehtml/_static/_sphinx_javascript_frameworks_compat.js
new file mode 100644
index 0000000..8141580
--- /dev/null
+++ b/singlehtml/_static/_sphinx_javascript_frameworks_compat.js
@@ -0,0 +1,123 @@
+/* Compatability shim for jQuery and underscores.js.
+ *
+ * Copyright Sphinx contributors
+ * Released under the two clause BSD licence
+ */
+
+/**
+ * small helper function to urldecode strings
+ *
+ * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL
+ */
+jQuery.urldecode = function(x) {
+ if (!x) {
+ return x
+ }
+ return decodeURIComponent(x.replace(/\+/g, ' '));
+};
+
+/**
+ * small helper function to urlencode strings
+ */
+jQuery.urlencode = encodeURIComponent;
+
+/**
+ * This function returns the parsed url parameters of the
+ * current request. Multiple values per key are supported,
+ * it will always return arrays of strings for the value parts.
+ */
+jQuery.getQueryParameters = function(s) {
+ if (typeof s === 'undefined')
+ s = document.location.search;
+ var parts = s.substr(s.indexOf('?') + 1).split('&');
+ var result = {};
+ for (var i = 0; i < parts.length; i++) {
+ var tmp = parts[i].split('=', 2);
+ var key = jQuery.urldecode(tmp[0]);
+ var value = jQuery.urldecode(tmp[1]);
+ if (key in result)
+ result[key].push(value);
+ else
+ result[key] = [value];
+ }
+ return result;
+};
+
+/**
+ * highlight a given string on a jquery object by wrapping it in
+ * span elements with the given class name.
+ */
+jQuery.fn.highlightText = function(text, className) {
+ function highlight(node, addItems) {
+ if (node.nodeType === 3) {
+ var val = node.nodeValue;
+ var pos = val.toLowerCase().indexOf(text);
+ if (pos >= 0 &&
+ !jQuery(node.parentNode).hasClass(className) &&
+ !jQuery(node.parentNode).hasClass("nohighlight")) {
+ var span;
+ var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg");
+ if (isInSVG) {
+ span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
+ } else {
+ span = document.createElement("span");
+ span.className = className;
+ }
+ span.appendChild(document.createTextNode(val.substr(pos, text.length)));
+ node.parentNode.insertBefore(span, node.parentNode.insertBefore(
+ document.createTextNode(val.substr(pos + text.length)),
+ node.nextSibling));
+ node.nodeValue = val.substr(0, pos);
+ if (isInSVG) {
+ var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
+ var bbox = node.parentElement.getBBox();
+ rect.x.baseVal.value = bbox.x;
+ rect.y.baseVal.value = bbox.y;
+ rect.width.baseVal.value = bbox.width;
+ rect.height.baseVal.value = bbox.height;
+ rect.setAttribute('class', className);
+ addItems.push({
+ "parent": node.parentNode,
+ "target": rect});
+ }
+ }
+ }
+ else if (!jQuery(node).is("button, select, textarea")) {
+ jQuery.each(node.childNodes, function() {
+ highlight(this, addItems);
+ });
+ }
+ }
+ var addItems = [];
+ var result = this.each(function() {
+ highlight(this, addItems);
+ });
+ for (var i = 0; i < addItems.length; ++i) {
+ jQuery(addItems[i].parent).before(addItems[i].target);
+ }
+ return result;
+};
+
+/*
+ * backward compatibility for jQuery.browser
+ * This will be supported until firefox bug is fixed.
+ */
+if (!jQuery.browser) {
+ jQuery.uaMatch = function(ua) {
+ ua = ua.toLowerCase();
+
+ var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
+ /(webkit)[ \/]([\w.]+)/.exec(ua) ||
+ /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
+ /(msie) ([\w.]+)/.exec(ua) ||
+ ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
+ [];
+
+ return {
+ browser: match[ 1 ] || "",
+ version: match[ 2 ] || "0"
+ };
+ };
+ jQuery.browser = {};
+ jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true;
+}
diff --git a/singlehtml/_static/basic.css b/singlehtml/_static/basic.css
new file mode 100644
index 0000000..f316efc
--- /dev/null
+++ b/singlehtml/_static/basic.css
@@ -0,0 +1,925 @@
+/*
+ * basic.css
+ * ~~~~~~~~~
+ *
+ * Sphinx stylesheet -- basic theme.
+ *
+ * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+/* -- main layout ----------------------------------------------------------- */
+
+div.clearer {
+ clear: both;
+}
+
+div.section::after {
+ display: block;
+ content: '';
+ clear: left;
+}
+
+/* -- relbar ---------------------------------------------------------------- */
+
+div.related {
+ width: 100%;
+ font-size: 90%;
+}
+
+div.related h3 {
+ display: none;
+}
+
+div.related ul {
+ margin: 0;
+ padding: 0 0 0 10px;
+ list-style: none;
+}
+
+div.related li {
+ display: inline;
+}
+
+div.related li.right {
+ float: right;
+ margin-right: 5px;
+}
+
+/* -- sidebar --------------------------------------------------------------- */
+
+div.sphinxsidebarwrapper {
+ padding: 10px 5px 0 10px;
+}
+
+div.sphinxsidebar {
+ float: left;
+ width: 230px;
+ margin-left: -100%;
+ font-size: 90%;
+ word-wrap: break-word;
+ overflow-wrap : break-word;
+}
+
+div.sphinxsidebar ul {
+ list-style: none;
+}
+
+div.sphinxsidebar ul ul,
+div.sphinxsidebar ul.want-points {
+ margin-left: 20px;
+ list-style: square;
+}
+
+div.sphinxsidebar ul ul {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+div.sphinxsidebar form {
+ margin-top: 10px;
+}
+
+div.sphinxsidebar input {
+ border: 1px solid #98dbcc;
+ font-family: sans-serif;
+ font-size: 1em;
+}
+
+div.sphinxsidebar #searchbox form.search {
+ overflow: hidden;
+}
+
+div.sphinxsidebar #searchbox input[type="text"] {
+ float: left;
+ width: 80%;
+ padding: 0.25em;
+ box-sizing: border-box;
+}
+
+div.sphinxsidebar #searchbox input[type="submit"] {
+ float: left;
+ width: 20%;
+ border-left: none;
+ padding: 0.25em;
+ box-sizing: border-box;
+}
+
+
+img {
+ border: 0;
+ max-width: 100%;
+}
+
+/* -- search page ----------------------------------------------------------- */
+
+ul.search {
+ margin: 10px 0 0 20px;
+ padding: 0;
+}
+
+ul.search li {
+ padding: 5px 0 5px 20px;
+ background-image: url(file.png);
+ background-repeat: no-repeat;
+ background-position: 0 7px;
+}
+
+ul.search li a {
+ font-weight: bold;
+}
+
+ul.search li p.context {
+ color: #888;
+ margin: 2px 0 0 30px;
+ text-align: left;
+}
+
+ul.keywordmatches li.goodmatch a {
+ font-weight: bold;
+}
+
+/* -- index page ------------------------------------------------------------ */
+
+table.contentstable {
+ width: 90%;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table.contentstable p.biglink {
+ line-height: 150%;
+}
+
+a.biglink {
+ font-size: 1.3em;
+}
+
+span.linkdescr {
+ font-style: italic;
+ padding-top: 5px;
+ font-size: 90%;
+}
+
+/* -- general index --------------------------------------------------------- */
+
+table.indextable {
+ width: 100%;
+}
+
+table.indextable td {
+ text-align: left;
+ vertical-align: top;
+}
+
+table.indextable ul {
+ margin-top: 0;
+ margin-bottom: 0;
+ list-style-type: none;
+}
+
+table.indextable > tbody > tr > td > ul {
+ padding-left: 0em;
+}
+
+table.indextable tr.pcap {
+ height: 10px;
+}
+
+table.indextable tr.cap {
+ margin-top: 10px;
+ background-color: #f2f2f2;
+}
+
+img.toggler {
+ margin-right: 3px;
+ margin-top: 3px;
+ cursor: pointer;
+}
+
+div.modindex-jumpbox {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ margin: 1em 0 1em 0;
+ padding: 0.4em;
+}
+
+div.genindex-jumpbox {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ margin: 1em 0 1em 0;
+ padding: 0.4em;
+}
+
+/* -- domain module index --------------------------------------------------- */
+
+table.modindextable td {
+ padding: 2px;
+ border-collapse: collapse;
+}
+
+/* -- general body styles --------------------------------------------------- */
+
+div.body {
+ min-width: 360px;
+ max-width: 800px;
+}
+
+div.body p, div.body dd, div.body li, div.body blockquote {
+ -moz-hyphens: auto;
+ -ms-hyphens: auto;
+ -webkit-hyphens: auto;
+ hyphens: auto;
+}
+
+a.headerlink {
+ visibility: hidden;
+}
+
+a:visited {
+ color: #551A8B;
+}
+
+h1:hover > a.headerlink,
+h2:hover > a.headerlink,
+h3:hover > a.headerlink,
+h4:hover > a.headerlink,
+h5:hover > a.headerlink,
+h6:hover > a.headerlink,
+dt:hover > a.headerlink,
+caption:hover > a.headerlink,
+p.caption:hover > a.headerlink,
+div.code-block-caption:hover > a.headerlink {
+ visibility: visible;
+}
+
+div.body p.caption {
+ text-align: inherit;
+}
+
+div.body td {
+ text-align: left;
+}
+
+.first {
+ margin-top: 0 !important;
+}
+
+p.rubric {
+ margin-top: 30px;
+ font-weight: bold;
+}
+
+img.align-left, figure.align-left, .figure.align-left, object.align-left {
+ clear: left;
+ float: left;
+ margin-right: 1em;
+}
+
+img.align-right, figure.align-right, .figure.align-right, object.align-right {
+ clear: right;
+ float: right;
+ margin-left: 1em;
+}
+
+img.align-center, figure.align-center, .figure.align-center, object.align-center {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+img.align-default, figure.align-default, .figure.align-default {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.align-left {
+ text-align: left;
+}
+
+.align-center {
+ text-align: center;
+}
+
+.align-default {
+ text-align: center;
+}
+
+.align-right {
+ text-align: right;
+}
+
+/* -- sidebars -------------------------------------------------------------- */
+
+div.sidebar,
+aside.sidebar {
+ margin: 0 0 0.5em 1em;
+ border: 1px solid #ddb;
+ padding: 7px;
+ background-color: #ffe;
+ width: 40%;
+ float: right;
+ clear: right;
+ overflow-x: auto;
+}
+
+p.sidebar-title {
+ font-weight: bold;
+}
+
+nav.contents,
+aside.topic,
+div.admonition, div.topic, blockquote {
+ clear: left;
+}
+
+/* -- topics ---------------------------------------------------------------- */
+
+nav.contents,
+aside.topic,
+div.topic {
+ border: 1px solid #ccc;
+ padding: 7px;
+ margin: 10px 0 10px 0;
+}
+
+p.topic-title {
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-top: 10px;
+}
+
+/* -- admonitions ----------------------------------------------------------- */
+
+div.admonition {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ padding: 7px;
+}
+
+div.admonition dt {
+ font-weight: bold;
+}
+
+p.admonition-title {
+ margin: 0px 10px 5px 0px;
+ font-weight: bold;
+}
+
+div.body p.centered {
+ text-align: center;
+ margin-top: 25px;
+}
+
+/* -- content of sidebars/topics/admonitions -------------------------------- */
+
+div.sidebar > :last-child,
+aside.sidebar > :last-child,
+nav.contents > :last-child,
+aside.topic > :last-child,
+div.topic > :last-child,
+div.admonition > :last-child {
+ margin-bottom: 0;
+}
+
+div.sidebar::after,
+aside.sidebar::after,
+nav.contents::after,
+aside.topic::after,
+div.topic::after,
+div.admonition::after,
+blockquote::after {
+ display: block;
+ content: '';
+ clear: both;
+}
+
+/* -- tables ---------------------------------------------------------------- */
+
+table.docutils {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ border: 0;
+ border-collapse: collapse;
+}
+
+table.align-center {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table.align-default {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+table caption span.caption-number {
+ font-style: italic;
+}
+
+table caption span.caption-text {
+}
+
+table.docutils td, table.docutils th {
+ padding: 1px 8px 1px 5px;
+ border-top: 0;
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 1px solid #aaa;
+}
+
+th {
+ text-align: left;
+ padding-right: 5px;
+}
+
+table.citation {
+ border-left: solid 1px gray;
+ margin-left: 1px;
+}
+
+table.citation td {
+ border-bottom: none;
+}
+
+th > :first-child,
+td > :first-child {
+ margin-top: 0px;
+}
+
+th > :last-child,
+td > :last-child {
+ margin-bottom: 0px;
+}
+
+/* -- figures --------------------------------------------------------------- */
+
+div.figure, figure {
+ margin: 0.5em;
+ padding: 0.5em;
+}
+
+div.figure p.caption, figcaption {
+ padding: 0.3em;
+}
+
+div.figure p.caption span.caption-number,
+figcaption span.caption-number {
+ font-style: italic;
+}
+
+div.figure p.caption span.caption-text,
+figcaption span.caption-text {
+}
+
+/* -- field list styles ----------------------------------------------------- */
+
+table.field-list td, table.field-list th {
+ border: 0 !important;
+}
+
+.field-list ul {
+ margin: 0;
+ padding-left: 1em;
+}
+
+.field-list p {
+ margin: 0;
+}
+
+.field-name {
+ -moz-hyphens: manual;
+ -ms-hyphens: manual;
+ -webkit-hyphens: manual;
+ hyphens: manual;
+}
+
+/* -- hlist styles ---------------------------------------------------------- */
+
+table.hlist {
+ margin: 1em 0;
+}
+
+table.hlist td {
+ vertical-align: top;
+}
+
+/* -- object description styles --------------------------------------------- */
+
+.sig {
+ font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+}
+
+.sig-name, code.descname {
+ background-color: transparent;
+ font-weight: bold;
+}
+
+.sig-name {
+ font-size: 1.1em;
+}
+
+code.descname {
+ font-size: 1.2em;
+}
+
+.sig-prename, code.descclassname {
+ background-color: transparent;
+}
+
+.optional {
+ font-size: 1.3em;
+}
+
+.sig-paren {
+ font-size: larger;
+}
+
+.sig-param.n {
+ font-style: italic;
+}
+
+/* C++ specific styling */
+
+.sig-inline.c-texpr,
+.sig-inline.cpp-texpr {
+ font-family: unset;
+}
+
+.sig.c .k, .sig.c .kt,
+.sig.cpp .k, .sig.cpp .kt {
+ color: #0033B3;
+}
+
+.sig.c .m,
+.sig.cpp .m {
+ color: #1750EB;
+}
+
+.sig.c .s, .sig.c .sc,
+.sig.cpp .s, .sig.cpp .sc {
+ color: #067D17;
+}
+
+
+/* -- other body styles ----------------------------------------------------- */
+
+ol.arabic {
+ list-style: decimal;
+}
+
+ol.loweralpha {
+ list-style: lower-alpha;
+}
+
+ol.upperalpha {
+ list-style: upper-alpha;
+}
+
+ol.lowerroman {
+ list-style: lower-roman;
+}
+
+ol.upperroman {
+ list-style: upper-roman;
+}
+
+:not(li) > ol > li:first-child > :first-child,
+:not(li) > ul > li:first-child > :first-child {
+ margin-top: 0px;
+}
+
+:not(li) > ol > li:last-child > :last-child,
+:not(li) > ul > li:last-child > :last-child {
+ margin-bottom: 0px;
+}
+
+ol.simple ol p,
+ol.simple ul p,
+ul.simple ol p,
+ul.simple ul p {
+ margin-top: 0;
+}
+
+ol.simple > li:not(:first-child) > p,
+ul.simple > li:not(:first-child) > p {
+ margin-top: 0;
+}
+
+ol.simple p,
+ul.simple p {
+ margin-bottom: 0;
+}
+
+aside.footnote > span,
+div.citation > span {
+ float: left;
+}
+aside.footnote > span:last-of-type,
+div.citation > span:last-of-type {
+ padding-right: 0.5em;
+}
+aside.footnote > p {
+ margin-left: 2em;
+}
+div.citation > p {
+ margin-left: 4em;
+}
+aside.footnote > p:last-of-type,
+div.citation > p:last-of-type {
+ margin-bottom: 0em;
+}
+aside.footnote > p:last-of-type:after,
+div.citation > p:last-of-type:after {
+ content: "";
+ clear: both;
+}
+
+dl.field-list {
+ display: grid;
+ grid-template-columns: fit-content(30%) auto;
+}
+
+dl.field-list > dt {
+ font-weight: bold;
+ word-break: break-word;
+ padding-left: 0.5em;
+ padding-right: 5px;
+}
+
+dl.field-list > dd {
+ padding-left: 0.5em;
+ margin-top: 0em;
+ margin-left: 0em;
+ margin-bottom: 0em;
+}
+
+dl {
+ margin-bottom: 15px;
+}
+
+dd > :first-child {
+ margin-top: 0px;
+}
+
+dd ul, dd table {
+ margin-bottom: 10px;
+}
+
+dd {
+ margin-top: 3px;
+ margin-bottom: 10px;
+ margin-left: 30px;
+}
+
+.sig dd {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+.sig dl {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+dl > dd:last-child,
+dl > dd:last-child > :last-child {
+ margin-bottom: 0;
+}
+
+dt:target, span.highlighted {
+ background-color: #fbe54e;
+}
+
+rect.highlighted {
+ fill: #fbe54e;
+}
+
+dl.glossary dt {
+ font-weight: bold;
+ font-size: 1.1em;
+}
+
+.versionmodified {
+ font-style: italic;
+}
+
+.system-message {
+ background-color: #fda;
+ padding: 5px;
+ border: 3px solid red;
+}
+
+.footnote:target {
+ background-color: #ffa;
+}
+
+.line-block {
+ display: block;
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+.line-block .line-block {
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-left: 1.5em;
+}
+
+.guilabel, .menuselection {
+ font-family: sans-serif;
+}
+
+.accelerator {
+ text-decoration: underline;
+}
+
+.classifier {
+ font-style: oblique;
+}
+
+.classifier:before {
+ font-style: normal;
+ margin: 0 0.5em;
+ content: ":";
+ display: inline-block;
+}
+
+abbr, acronym {
+ border-bottom: dotted 1px;
+ cursor: help;
+}
+
+.translated {
+ background-color: rgba(207, 255, 207, 0.2)
+}
+
+.untranslated {
+ background-color: rgba(255, 207, 207, 0.2)
+}
+
+/* -- code displays --------------------------------------------------------- */
+
+pre {
+ overflow: auto;
+ overflow-y: hidden; /* fixes display issues on Chrome browsers */
+}
+
+pre, div[class*="highlight-"] {
+ clear: both;
+}
+
+span.pre {
+ -moz-hyphens: none;
+ -ms-hyphens: none;
+ -webkit-hyphens: none;
+ hyphens: none;
+ white-space: nowrap;
+}
+
+div[class*="highlight-"] {
+ margin: 1em 0;
+}
+
+td.linenos pre {
+ border: 0;
+ background-color: transparent;
+ color: #aaa;
+}
+
+table.highlighttable {
+ display: block;
+}
+
+table.highlighttable tbody {
+ display: block;
+}
+
+table.highlighttable tr {
+ display: flex;
+}
+
+table.highlighttable td {
+ margin: 0;
+ padding: 0;
+}
+
+table.highlighttable td.linenos {
+ padding-right: 0.5em;
+}
+
+table.highlighttable td.code {
+ flex: 1;
+ overflow: hidden;
+}
+
+.highlight .hll {
+ display: block;
+}
+
+div.highlight pre,
+table.highlighttable pre {
+ margin: 0;
+}
+
+div.code-block-caption + div {
+ margin-top: 0;
+}
+
+div.code-block-caption {
+ margin-top: 1em;
+ padding: 2px 5px;
+ font-size: small;
+}
+
+div.code-block-caption code {
+ background-color: transparent;
+}
+
+table.highlighttable td.linenos,
+span.linenos,
+div.highlight span.gp { /* gp: Generic.Prompt */
+ user-select: none;
+ -webkit-user-select: text; /* Safari fallback only */
+ -webkit-user-select: none; /* Chrome/Safari */
+ -moz-user-select: none; /* Firefox */
+ -ms-user-select: none; /* IE10+ */
+}
+
+div.code-block-caption span.caption-number {
+ padding: 0.1em 0.3em;
+ font-style: italic;
+}
+
+div.code-block-caption span.caption-text {
+}
+
+div.literal-block-wrapper {
+ margin: 1em 0;
+}
+
+code.xref, a code {
+ background-color: transparent;
+ font-weight: bold;
+}
+
+h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
+ background-color: transparent;
+}
+
+.viewcode-link {
+ float: right;
+}
+
+.viewcode-back {
+ float: right;
+ font-family: sans-serif;
+}
+
+div.viewcode-block:target {
+ margin: -1px -10px;
+ padding: 0 10px;
+}
+
+/* -- math display ---------------------------------------------------------- */
+
+img.math {
+ vertical-align: middle;
+}
+
+div.body div.math p {
+ text-align: center;
+}
+
+span.eqno {
+ float: right;
+}
+
+span.eqno a.headerlink {
+ position: absolute;
+ z-index: 1;
+}
+
+div.math:hover a.headerlink {
+ visibility: visible;
+}
+
+/* -- printout stylesheet --------------------------------------------------- */
+
+@media print {
+ div.document,
+ div.documentwrapper,
+ div.bodywrapper {
+ margin: 0 !important;
+ width: 100%;
+ }
+
+ div.sphinxsidebar,
+ div.related,
+ div.footer,
+ #top-link {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/singlehtml/_static/check-solid.svg b/singlehtml/_static/check-solid.svg
new file mode 100644
index 0000000..92fad4b
--- /dev/null
+++ b/singlehtml/_static/check-solid.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/singlehtml/_static/clipboard.min.js b/singlehtml/_static/clipboard.min.js
new file mode 100644
index 0000000..54b3c46
--- /dev/null
+++ b/singlehtml/_static/clipboard.min.js
@@ -0,0 +1,7 @@
+/*!
+ * clipboard.js v2.0.8
+ * https://clipboardjs.com/
+ *
+ * Licensed MIT © Zeno Rocha
+ */
+!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1
+
+
+
+
diff --git a/singlehtml/_static/copybutton.css b/singlehtml/_static/copybutton.css
new file mode 100644
index 0000000..f1916ec
--- /dev/null
+++ b/singlehtml/_static/copybutton.css
@@ -0,0 +1,94 @@
+/* Copy buttons */
+button.copybtn {
+ position: absolute;
+ display: flex;
+ top: .3em;
+ right: .3em;
+ width: 1.7em;
+ height: 1.7em;
+ opacity: 0;
+ transition: opacity 0.3s, border .3s, background-color .3s;
+ user-select: none;
+ padding: 0;
+ border: none;
+ outline: none;
+ border-radius: 0.4em;
+ /* The colors that GitHub uses */
+ border: #1b1f2426 1px solid;
+ background-color: #f6f8fa;
+ color: #57606a;
+}
+
+button.copybtn.success {
+ border-color: #22863a;
+ color: #22863a;
+}
+
+button.copybtn svg {
+ stroke: currentColor;
+ width: 1.5em;
+ height: 1.5em;
+ padding: 0.1em;
+}
+
+div.highlight {
+ position: relative;
+}
+
+/* Show the copybutton */
+.highlight:hover button.copybtn, button.copybtn.success {
+ opacity: 1;
+}
+
+.highlight button.copybtn:hover {
+ background-color: rgb(235, 235, 235);
+}
+
+.highlight button.copybtn:active {
+ background-color: rgb(187, 187, 187);
+}
+
+/**
+ * A minimal CSS-only tooltip copied from:
+ * https://codepen.io/mildrenben/pen/rVBrpK
+ *
+ * To use, write HTML like the following:
+ *
+ * Short
+ */
+ .o-tooltip--left {
+ position: relative;
+ }
+
+ .o-tooltip--left:after {
+ opacity: 0;
+ visibility: hidden;
+ position: absolute;
+ content: attr(data-tooltip);
+ padding: .2em;
+ font-size: .8em;
+ left: -.2em;
+ background: grey;
+ color: white;
+ white-space: nowrap;
+ z-index: 2;
+ border-radius: 2px;
+ transform: translateX(-102%) translateY(0);
+ transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1);
+}
+
+.o-tooltip--left:hover:after {
+ display: block;
+ opacity: 1;
+ visibility: visible;
+ transform: translateX(-100%) translateY(0);
+ transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1);
+ transition-delay: .5s;
+}
+
+/* By default the copy button shouldn't show up when printing a page */
+@media print {
+ button.copybtn {
+ display: none;
+ }
+}
diff --git a/singlehtml/_static/copybutton.js b/singlehtml/_static/copybutton.js
new file mode 100644
index 0000000..b398703
--- /dev/null
+++ b/singlehtml/_static/copybutton.js
@@ -0,0 +1,248 @@
+// Localization support
+const messages = {
+ 'en': {
+ 'copy': 'Copy',
+ 'copy_to_clipboard': 'Copy to clipboard',
+ 'copy_success': 'Copied!',
+ 'copy_failure': 'Failed to copy',
+ },
+ 'es' : {
+ 'copy': 'Copiar',
+ 'copy_to_clipboard': 'Copiar al portapapeles',
+ 'copy_success': '¡Copiado!',
+ 'copy_failure': 'Error al copiar',
+ },
+ 'de' : {
+ 'copy': 'Kopieren',
+ 'copy_to_clipboard': 'In die Zwischenablage kopieren',
+ 'copy_success': 'Kopiert!',
+ 'copy_failure': 'Fehler beim Kopieren',
+ },
+ 'fr' : {
+ 'copy': 'Copier',
+ 'copy_to_clipboard': 'Copier dans le presse-papier',
+ 'copy_success': 'Copié !',
+ 'copy_failure': 'Échec de la copie',
+ },
+ 'ru': {
+ 'copy': 'Скопировать',
+ 'copy_to_clipboard': 'Скопировать в буфер',
+ 'copy_success': 'Скопировано!',
+ 'copy_failure': 'Не удалось скопировать',
+ },
+ 'zh-CN': {
+ 'copy': '复制',
+ 'copy_to_clipboard': '复制到剪贴板',
+ 'copy_success': '复制成功!',
+ 'copy_failure': '复制失败',
+ },
+ 'it' : {
+ 'copy': 'Copiare',
+ 'copy_to_clipboard': 'Copiato negli appunti',
+ 'copy_success': 'Copiato!',
+ 'copy_failure': 'Errore durante la copia',
+ }
+}
+
+let locale = 'en'
+if( document.documentElement.lang !== undefined
+ && messages[document.documentElement.lang] !== undefined ) {
+ locale = document.documentElement.lang
+}
+
+let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT;
+if (doc_url_root == '#') {
+ doc_url_root = '';
+}
+
+/**
+ * SVG files for our copy buttons
+ */
+let iconCheck = `
+ ${messages[locale]['copy_success']}
+
+
+ `
+
+// If the user specified their own SVG use that, otherwise use the default
+let iconCopy = ``;
+if (!iconCopy) {
+ iconCopy = `
+ ${messages[locale]['copy_to_clipboard']}
+
+
+
+ `
+}
+
+/**
+ * Set up copy/paste for code blocks
+ */
+
+const runWhenDOMLoaded = cb => {
+ if (document.readyState != 'loading') {
+ cb()
+ } else if (document.addEventListener) {
+ document.addEventListener('DOMContentLoaded', cb)
+ } else {
+ document.attachEvent('onreadystatechange', function() {
+ if (document.readyState == 'complete') cb()
+ })
+ }
+}
+
+const codeCellId = index => `codecell${index}`
+
+// Clears selected text since ClipboardJS will select the text when copying
+const clearSelection = () => {
+ if (window.getSelection) {
+ window.getSelection().removeAllRanges()
+ } else if (document.selection) {
+ document.selection.empty()
+ }
+}
+
+// Changes tooltip text for a moment, then changes it back
+// We want the timeout of our `success` class to be a bit shorter than the
+// tooltip and icon change, so that we can hide the icon before changing back.
+var timeoutIcon = 2000;
+var timeoutSuccessClass = 1500;
+
+const temporarilyChangeTooltip = (el, oldText, newText) => {
+ el.setAttribute('data-tooltip', newText)
+ el.classList.add('success')
+ // Remove success a little bit sooner than we change the tooltip
+ // So that we can use CSS to hide the copybutton first
+ setTimeout(() => el.classList.remove('success'), timeoutSuccessClass)
+ setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon)
+}
+
+// Changes the copy button icon for two seconds, then changes it back
+const temporarilyChangeIcon = (el) => {
+ el.innerHTML = iconCheck;
+ setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon)
+}
+
+const addCopyButtonToCodeCells = () => {
+ // If ClipboardJS hasn't loaded, wait a bit and try again. This
+ // happens because we load ClipboardJS asynchronously.
+ if (window.ClipboardJS === undefined) {
+ setTimeout(addCopyButtonToCodeCells, 250)
+ return
+ }
+
+ // Add copybuttons to all of our code cells
+ const COPYBUTTON_SELECTOR = 'div.highlight pre';
+ const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR)
+ codeCells.forEach((codeCell, index) => {
+ const id = codeCellId(index)
+ codeCell.setAttribute('id', id)
+
+ const clipboardButton = id =>
+ `
+ ${iconCopy}
+ `
+ codeCell.insertAdjacentHTML('afterend', clipboardButton(id))
+ })
+
+function escapeRegExp(string) {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
+}
+
+/**
+ * Removes excluded text from a Node.
+ *
+ * @param {Node} target Node to filter.
+ * @param {string} exclude CSS selector of nodes to exclude.
+ * @returns {DOMString} Text from `target` with text removed.
+ */
+function filterText(target, exclude) {
+ const clone = target.cloneNode(true); // clone as to not modify the live DOM
+ if (exclude) {
+ // remove excluded nodes
+ clone.querySelectorAll(exclude).forEach(node => node.remove());
+ }
+ return clone.innerText;
+}
+
+// Callback when a copy button is clicked. Will be passed the node that was clicked
+// should then grab the text and replace pieces of text that shouldn't be used in output
+function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") {
+ var regexp;
+ var match;
+
+ // Do we check for line continuation characters and "HERE-documents"?
+ var useLineCont = !!lineContinuationChar
+ var useHereDoc = !!hereDocDelim
+
+ // create regexp to capture prompt and remaining line
+ if (isRegexp) {
+ regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)')
+ } else {
+ regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)')
+ }
+
+ const outputLines = [];
+ var promptFound = false;
+ var gotLineCont = false;
+ var gotHereDoc = false;
+ const lineGotPrompt = [];
+ for (const line of textContent.split('\n')) {
+ match = line.match(regexp)
+ if (match || gotLineCont || gotHereDoc) {
+ promptFound = regexp.test(line)
+ lineGotPrompt.push(promptFound)
+ if (removePrompts && promptFound) {
+ outputLines.push(match[2])
+ } else {
+ outputLines.push(line)
+ }
+ gotLineCont = line.endsWith(lineContinuationChar) & useLineCont
+ if (line.includes(hereDocDelim) & useHereDoc)
+ gotHereDoc = !gotHereDoc
+ } else if (!onlyCopyPromptLines) {
+ outputLines.push(line)
+ } else if (copyEmptyLines && line.trim() === '') {
+ outputLines.push(line)
+ }
+ }
+
+ // If no lines with the prompt were found then just use original lines
+ if (lineGotPrompt.some(v => v === true)) {
+ textContent = outputLines.join('\n');
+ }
+
+ // Remove a trailing newline to avoid auto-running when pasting
+ if (textContent.endsWith("\n")) {
+ textContent = textContent.slice(0, -1)
+ }
+ return textContent
+}
+
+
+var copyTargetText = (trigger) => {
+ var target = document.querySelector(trigger.attributes['data-clipboard-target'].value);
+
+ // get filtered text
+ let exclude = '.linenos, .gp';
+
+ let text = filterText(target, exclude);
+ return formatCopyText(text, '', false, true, true, true, '', '')
+}
+
+ // Initialize with a callback so we can modify the text before copy
+ const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText})
+
+ // Update UI with error/success messages
+ clipboard.on('success', event => {
+ clearSelection()
+ temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success'])
+ temporarilyChangeIcon(event.trigger)
+ })
+
+ clipboard.on('error', event => {
+ temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure'])
+ })
+}
+
+runWhenDOMLoaded(addCopyButtonToCodeCells)
\ No newline at end of file
diff --git a/singlehtml/_static/copybutton_funcs.js b/singlehtml/_static/copybutton_funcs.js
new file mode 100644
index 0000000..dbe1aaa
--- /dev/null
+++ b/singlehtml/_static/copybutton_funcs.js
@@ -0,0 +1,73 @@
+function escapeRegExp(string) {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
+}
+
+/**
+ * Removes excluded text from a Node.
+ *
+ * @param {Node} target Node to filter.
+ * @param {string} exclude CSS selector of nodes to exclude.
+ * @returns {DOMString} Text from `target` with text removed.
+ */
+export function filterText(target, exclude) {
+ const clone = target.cloneNode(true); // clone as to not modify the live DOM
+ if (exclude) {
+ // remove excluded nodes
+ clone.querySelectorAll(exclude).forEach(node => node.remove());
+ }
+ return clone.innerText;
+}
+
+// Callback when a copy button is clicked. Will be passed the node that was clicked
+// should then grab the text and replace pieces of text that shouldn't be used in output
+export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") {
+ var regexp;
+ var match;
+
+ // Do we check for line continuation characters and "HERE-documents"?
+ var useLineCont = !!lineContinuationChar
+ var useHereDoc = !!hereDocDelim
+
+ // create regexp to capture prompt and remaining line
+ if (isRegexp) {
+ regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)')
+ } else {
+ regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)')
+ }
+
+ const outputLines = [];
+ var promptFound = false;
+ var gotLineCont = false;
+ var gotHereDoc = false;
+ const lineGotPrompt = [];
+ for (const line of textContent.split('\n')) {
+ match = line.match(regexp)
+ if (match || gotLineCont || gotHereDoc) {
+ promptFound = regexp.test(line)
+ lineGotPrompt.push(promptFound)
+ if (removePrompts && promptFound) {
+ outputLines.push(match[2])
+ } else {
+ outputLines.push(line)
+ }
+ gotLineCont = line.endsWith(lineContinuationChar) & useLineCont
+ if (line.includes(hereDocDelim) & useHereDoc)
+ gotHereDoc = !gotHereDoc
+ } else if (!onlyCopyPromptLines) {
+ outputLines.push(line)
+ } else if (copyEmptyLines && line.trim() === '') {
+ outputLines.push(line)
+ }
+ }
+
+ // If no lines with the prompt were found then just use original lines
+ if (lineGotPrompt.some(v => v === true)) {
+ textContent = outputLines.join('\n');
+ }
+
+ // Remove a trailing newline to avoid auto-running when pasting
+ if (textContent.endsWith("\n")) {
+ textContent = textContent.slice(0, -1)
+ }
+ return textContent
+}
diff --git a/singlehtml/_static/css/badge_only.css b/singlehtml/_static/css/badge_only.css
new file mode 100644
index 0000000..88ba55b
--- /dev/null
+++ b/singlehtml/_static/css/badge_only.css
@@ -0,0 +1 @@
+.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}
\ No newline at end of file
diff --git a/singlehtml/_static/css/fonts/Roboto-Slab-Bold.woff b/singlehtml/_static/css/fonts/Roboto-Slab-Bold.woff
new file mode 100644
index 0000000..6cb6000
Binary files /dev/null and b/singlehtml/_static/css/fonts/Roboto-Slab-Bold.woff differ
diff --git a/singlehtml/_static/css/fonts/Roboto-Slab-Bold.woff2 b/singlehtml/_static/css/fonts/Roboto-Slab-Bold.woff2
new file mode 100644
index 0000000..7059e23
Binary files /dev/null and b/singlehtml/_static/css/fonts/Roboto-Slab-Bold.woff2 differ
diff --git a/singlehtml/_static/css/fonts/Roboto-Slab-Regular.woff b/singlehtml/_static/css/fonts/Roboto-Slab-Regular.woff
new file mode 100644
index 0000000..f815f63
Binary files /dev/null and b/singlehtml/_static/css/fonts/Roboto-Slab-Regular.woff differ
diff --git a/singlehtml/_static/css/fonts/Roboto-Slab-Regular.woff2 b/singlehtml/_static/css/fonts/Roboto-Slab-Regular.woff2
new file mode 100644
index 0000000..f2c76e5
Binary files /dev/null and b/singlehtml/_static/css/fonts/Roboto-Slab-Regular.woff2 differ
diff --git a/singlehtml/_static/css/fonts/fontawesome-webfont.eot b/singlehtml/_static/css/fonts/fontawesome-webfont.eot
new file mode 100644
index 0000000..e9f60ca
Binary files /dev/null and b/singlehtml/_static/css/fonts/fontawesome-webfont.eot differ
diff --git a/singlehtml/_static/css/fonts/fontawesome-webfont.svg b/singlehtml/_static/css/fonts/fontawesome-webfont.svg
new file mode 100644
index 0000000..855c845
--- /dev/null
+++ b/singlehtml/_static/css/fonts/fontawesome-webfont.svg
@@ -0,0 +1,2671 @@
+
+
+
+
+Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016
+ By ,,,
+Copyright Dave Gandy 2016. All rights reserved.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/singlehtml/_static/css/fonts/fontawesome-webfont.ttf b/singlehtml/_static/css/fonts/fontawesome-webfont.ttf
new file mode 100644
index 0000000..35acda2
Binary files /dev/null and b/singlehtml/_static/css/fonts/fontawesome-webfont.ttf differ
diff --git a/singlehtml/_static/css/fonts/fontawesome-webfont.woff b/singlehtml/_static/css/fonts/fontawesome-webfont.woff
new file mode 100644
index 0000000..400014a
Binary files /dev/null and b/singlehtml/_static/css/fonts/fontawesome-webfont.woff differ
diff --git a/singlehtml/_static/css/fonts/fontawesome-webfont.woff2 b/singlehtml/_static/css/fonts/fontawesome-webfont.woff2
new file mode 100644
index 0000000..4d13fc6
Binary files /dev/null and b/singlehtml/_static/css/fonts/fontawesome-webfont.woff2 differ
diff --git a/singlehtml/_static/css/fonts/lato-bold-italic.woff b/singlehtml/_static/css/fonts/lato-bold-italic.woff
new file mode 100644
index 0000000..88ad05b
Binary files /dev/null and b/singlehtml/_static/css/fonts/lato-bold-italic.woff differ
diff --git a/singlehtml/_static/css/fonts/lato-bold-italic.woff2 b/singlehtml/_static/css/fonts/lato-bold-italic.woff2
new file mode 100644
index 0000000..c4e3d80
Binary files /dev/null and b/singlehtml/_static/css/fonts/lato-bold-italic.woff2 differ
diff --git a/singlehtml/_static/css/fonts/lato-bold.woff b/singlehtml/_static/css/fonts/lato-bold.woff
new file mode 100644
index 0000000..c6dff51
Binary files /dev/null and b/singlehtml/_static/css/fonts/lato-bold.woff differ
diff --git a/singlehtml/_static/css/fonts/lato-bold.woff2 b/singlehtml/_static/css/fonts/lato-bold.woff2
new file mode 100644
index 0000000..bb19504
Binary files /dev/null and b/singlehtml/_static/css/fonts/lato-bold.woff2 differ
diff --git a/singlehtml/_static/css/fonts/lato-normal-italic.woff b/singlehtml/_static/css/fonts/lato-normal-italic.woff
new file mode 100644
index 0000000..76114bc
Binary files /dev/null and b/singlehtml/_static/css/fonts/lato-normal-italic.woff differ
diff --git a/singlehtml/_static/css/fonts/lato-normal-italic.woff2 b/singlehtml/_static/css/fonts/lato-normal-italic.woff2
new file mode 100644
index 0000000..3404f37
Binary files /dev/null and b/singlehtml/_static/css/fonts/lato-normal-italic.woff2 differ
diff --git a/singlehtml/_static/css/fonts/lato-normal.woff b/singlehtml/_static/css/fonts/lato-normal.woff
new file mode 100644
index 0000000..ae1307f
Binary files /dev/null and b/singlehtml/_static/css/fonts/lato-normal.woff differ
diff --git a/singlehtml/_static/css/fonts/lato-normal.woff2 b/singlehtml/_static/css/fonts/lato-normal.woff2
new file mode 100644
index 0000000..3bf9843
Binary files /dev/null and b/singlehtml/_static/css/fonts/lato-normal.woff2 differ
diff --git a/singlehtml/_static/css/theme.css b/singlehtml/_static/css/theme.css
new file mode 100644
index 0000000..0f14f10
--- /dev/null
+++ b/singlehtml/_static/css/theme.css
@@ -0,0 +1,4 @@
+html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*!
+ * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
+ * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search .wy-dropdown>aactive,.wy-side-nav-search .wy-dropdown>afocus,.wy-side-nav-search>a:hover,.wy-side-nav-search>aactive,.wy-side-nav-search>afocus{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon,.wy-side-nav-search>a.icon{display:block}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.switch-menus{position:relative;display:block;margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-side-nav-search>div.switch-menus>div.language-switch,.wy-side-nav-search>div.switch-menus>div.version-switch{display:inline-block;padding:.2em}.wy-side-nav-search>div.switch-menus>div.language-switch select,.wy-side-nav-search>div.switch-menus>div.version-switch select{display:inline-block;margin-right:-2rem;padding-right:2rem;max-width:240px;text-align-last:center;background:none;border:none;border-radius:0;box-shadow:none;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-size:1em;font-weight:400;color:hsla(0,0%,100%,.3);cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none}.wy-side-nav-search>div.switch-menus>div.language-switch select:active,.wy-side-nav-search>div.switch-menus>div.language-switch select:focus,.wy-side-nav-search>div.switch-menus>div.language-switch select:hover,.wy-side-nav-search>div.switch-menus>div.version-switch select:active,.wy-side-nav-search>div.switch-menus>div.version-switch select:focus,.wy-side-nav-search>div.switch-menus>div.version-switch select:hover{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.wy-side-nav-search>div.switch-menus>div.language-switch select option,.wy-side-nav-search>div.switch-menus>div.version-switch select option{color:#000}.wy-side-nav-search>div.switch-menus>div.language-switch:has(>select):after,.wy-side-nav-search>div.switch-menus>div.version-switch:has(>select):after{display:inline-block;width:1.5em;height:100%;padding:.1em;content:"\f0d7";font-size:1em;line-height:1.2em;font-family:FontAwesome;text-align:center;pointer-events:none;box-sizing:border-box}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block}
\ No newline at end of file
diff --git a/singlehtml/_static/doctools.js b/singlehtml/_static/doctools.js
new file mode 100644
index 0000000..4d67807
--- /dev/null
+++ b/singlehtml/_static/doctools.js
@@ -0,0 +1,156 @@
+/*
+ * doctools.js
+ * ~~~~~~~~~~~
+ *
+ * Base JavaScript utilities for all Sphinx HTML documentation.
+ *
+ * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+"use strict";
+
+const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([
+ "TEXTAREA",
+ "INPUT",
+ "SELECT",
+ "BUTTON",
+]);
+
+const _ready = (callback) => {
+ if (document.readyState !== "loading") {
+ callback();
+ } else {
+ document.addEventListener("DOMContentLoaded", callback);
+ }
+};
+
+/**
+ * Small JavaScript module for the documentation.
+ */
+const Documentation = {
+ init: () => {
+ Documentation.initDomainIndexTable();
+ Documentation.initOnKeyListeners();
+ },
+
+ /**
+ * i18n support
+ */
+ TRANSLATIONS: {},
+ PLURAL_EXPR: (n) => (n === 1 ? 0 : 1),
+ LOCALE: "unknown",
+
+ // gettext and ngettext don't access this so that the functions
+ // can safely bound to a different name (_ = Documentation.gettext)
+ gettext: (string) => {
+ const translated = Documentation.TRANSLATIONS[string];
+ switch (typeof translated) {
+ case "undefined":
+ return string; // no translation
+ case "string":
+ return translated; // translation exists
+ default:
+ return translated[0]; // (singular, plural) translation tuple exists
+ }
+ },
+
+ ngettext: (singular, plural, n) => {
+ const translated = Documentation.TRANSLATIONS[singular];
+ if (typeof translated !== "undefined")
+ return translated[Documentation.PLURAL_EXPR(n)];
+ return n === 1 ? singular : plural;
+ },
+
+ addTranslations: (catalog) => {
+ Object.assign(Documentation.TRANSLATIONS, catalog.messages);
+ Documentation.PLURAL_EXPR = new Function(
+ "n",
+ `return (${catalog.plural_expr})`
+ );
+ Documentation.LOCALE = catalog.locale;
+ },
+
+ /**
+ * helper function to focus on search bar
+ */
+ focusSearchBar: () => {
+ document.querySelectorAll("input[name=q]")[0]?.focus();
+ },
+
+ /**
+ * Initialise the domain index toggle buttons
+ */
+ initDomainIndexTable: () => {
+ const toggler = (el) => {
+ const idNumber = el.id.substr(7);
+ const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`);
+ if (el.src.substr(-9) === "minus.png") {
+ el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`;
+ toggledRows.forEach((el) => (el.style.display = "none"));
+ } else {
+ el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`;
+ toggledRows.forEach((el) => (el.style.display = ""));
+ }
+ };
+
+ const togglerElements = document.querySelectorAll("img.toggler");
+ togglerElements.forEach((el) =>
+ el.addEventListener("click", (event) => toggler(event.currentTarget))
+ );
+ togglerElements.forEach((el) => (el.style.display = ""));
+ if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler);
+ },
+
+ initOnKeyListeners: () => {
+ // only install a listener if it is really needed
+ if (
+ !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS &&
+ !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS
+ )
+ return;
+
+ document.addEventListener("keydown", (event) => {
+ // bail for input elements
+ if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
+ // bail with special keys
+ if (event.altKey || event.ctrlKey || event.metaKey) return;
+
+ if (!event.shiftKey) {
+ switch (event.key) {
+ case "ArrowLeft":
+ if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
+
+ const prevLink = document.querySelector('link[rel="prev"]');
+ if (prevLink && prevLink.href) {
+ window.location.href = prevLink.href;
+ event.preventDefault();
+ }
+ break;
+ case "ArrowRight":
+ if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
+
+ const nextLink = document.querySelector('link[rel="next"]');
+ if (nextLink && nextLink.href) {
+ window.location.href = nextLink.href;
+ event.preventDefault();
+ }
+ break;
+ }
+ }
+
+ // some keyboard layouts may need Shift to get /
+ switch (event.key) {
+ case "/":
+ if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break;
+ Documentation.focusSearchBar();
+ event.preventDefault();
+ }
+ });
+ },
+};
+
+// quick alias for translations
+const _ = Documentation.gettext;
+
+_ready(Documentation.init);
diff --git a/singlehtml/_static/documentation_options.js b/singlehtml/_static/documentation_options.js
new file mode 100644
index 0000000..89003c6
--- /dev/null
+++ b/singlehtml/_static/documentation_options.js
@@ -0,0 +1,13 @@
+const DOCUMENTATION_OPTIONS = {
+ VERSION: '',
+ LANGUAGE: 'en',
+ COLLAPSE_INDEX: false,
+ BUILDER: 'singlehtml',
+ FILE_SUFFIX: '.html',
+ LINK_SUFFIX: '.html',
+ HAS_SOURCE: true,
+ SOURCELINK_SUFFIX: '.txt',
+ NAVIGATION_WITH_KEYS: false,
+ SHOW_SEARCH_SUMMARY: true,
+ ENABLE_SEARCH_SHORTCUTS: true,
+};
\ No newline at end of file
diff --git a/singlehtml/_static/file.png b/singlehtml/_static/file.png
new file mode 100644
index 0000000..a858a41
Binary files /dev/null and b/singlehtml/_static/file.png differ
diff --git a/singlehtml/_static/fonts/Lato/lato-bold.eot b/singlehtml/_static/fonts/Lato/lato-bold.eot
new file mode 100644
index 0000000..3361183
Binary files /dev/null and b/singlehtml/_static/fonts/Lato/lato-bold.eot differ
diff --git a/singlehtml/_static/fonts/Lato/lato-bold.ttf b/singlehtml/_static/fonts/Lato/lato-bold.ttf
new file mode 100644
index 0000000..29f691d
Binary files /dev/null and b/singlehtml/_static/fonts/Lato/lato-bold.ttf differ
diff --git a/singlehtml/_static/fonts/Lato/lato-bold.woff b/singlehtml/_static/fonts/Lato/lato-bold.woff
new file mode 100644
index 0000000..c6dff51
Binary files /dev/null and b/singlehtml/_static/fonts/Lato/lato-bold.woff differ
diff --git a/singlehtml/_static/fonts/Lato/lato-bold.woff2 b/singlehtml/_static/fonts/Lato/lato-bold.woff2
new file mode 100644
index 0000000..bb19504
Binary files /dev/null and b/singlehtml/_static/fonts/Lato/lato-bold.woff2 differ
diff --git a/singlehtml/_static/fonts/Lato/lato-bolditalic.eot b/singlehtml/_static/fonts/Lato/lato-bolditalic.eot
new file mode 100644
index 0000000..3d41549
Binary files /dev/null and b/singlehtml/_static/fonts/Lato/lato-bolditalic.eot differ
diff --git a/singlehtml/_static/fonts/Lato/lato-bolditalic.ttf b/singlehtml/_static/fonts/Lato/lato-bolditalic.ttf
new file mode 100644
index 0000000..f402040
Binary files /dev/null and b/singlehtml/_static/fonts/Lato/lato-bolditalic.ttf differ
diff --git a/singlehtml/_static/fonts/Lato/lato-bolditalic.woff b/singlehtml/_static/fonts/Lato/lato-bolditalic.woff
new file mode 100644
index 0000000..88ad05b
Binary files /dev/null and b/singlehtml/_static/fonts/Lato/lato-bolditalic.woff differ
diff --git a/singlehtml/_static/fonts/Lato/lato-bolditalic.woff2 b/singlehtml/_static/fonts/Lato/lato-bolditalic.woff2
new file mode 100644
index 0000000..c4e3d80
Binary files /dev/null and b/singlehtml/_static/fonts/Lato/lato-bolditalic.woff2 differ
diff --git a/singlehtml/_static/fonts/Lato/lato-italic.eot b/singlehtml/_static/fonts/Lato/lato-italic.eot
new file mode 100644
index 0000000..3f82642
Binary files /dev/null and b/singlehtml/_static/fonts/Lato/lato-italic.eot differ
diff --git a/singlehtml/_static/fonts/Lato/lato-italic.ttf b/singlehtml/_static/fonts/Lato/lato-italic.ttf
new file mode 100644
index 0000000..b4bfc9b
Binary files /dev/null and b/singlehtml/_static/fonts/Lato/lato-italic.ttf differ
diff --git a/singlehtml/_static/fonts/Lato/lato-italic.woff b/singlehtml/_static/fonts/Lato/lato-italic.woff
new file mode 100644
index 0000000..76114bc
Binary files /dev/null and b/singlehtml/_static/fonts/Lato/lato-italic.woff differ
diff --git a/singlehtml/_static/fonts/Lato/lato-italic.woff2 b/singlehtml/_static/fonts/Lato/lato-italic.woff2
new file mode 100644
index 0000000..3404f37
Binary files /dev/null and b/singlehtml/_static/fonts/Lato/lato-italic.woff2 differ
diff --git a/singlehtml/_static/fonts/Lato/lato-regular.eot b/singlehtml/_static/fonts/Lato/lato-regular.eot
new file mode 100644
index 0000000..11e3f2a
Binary files /dev/null and b/singlehtml/_static/fonts/Lato/lato-regular.eot differ
diff --git a/singlehtml/_static/fonts/Lato/lato-regular.ttf b/singlehtml/_static/fonts/Lato/lato-regular.ttf
new file mode 100644
index 0000000..74decd9
Binary files /dev/null and b/singlehtml/_static/fonts/Lato/lato-regular.ttf differ
diff --git a/singlehtml/_static/fonts/Lato/lato-regular.woff b/singlehtml/_static/fonts/Lato/lato-regular.woff
new file mode 100644
index 0000000..ae1307f
Binary files /dev/null and b/singlehtml/_static/fonts/Lato/lato-regular.woff differ
diff --git a/singlehtml/_static/fonts/Lato/lato-regular.woff2 b/singlehtml/_static/fonts/Lato/lato-regular.woff2
new file mode 100644
index 0000000..3bf9843
Binary files /dev/null and b/singlehtml/_static/fonts/Lato/lato-regular.woff2 differ
diff --git a/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot
new file mode 100644
index 0000000..79dc8ef
Binary files /dev/null and b/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot differ
diff --git a/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf
new file mode 100644
index 0000000..df5d1df
Binary files /dev/null and b/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf differ
diff --git a/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff
new file mode 100644
index 0000000..6cb6000
Binary files /dev/null and b/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff differ
diff --git a/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2
new file mode 100644
index 0000000..7059e23
Binary files /dev/null and b/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 differ
diff --git a/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot
new file mode 100644
index 0000000..2f7ca78
Binary files /dev/null and b/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot differ
diff --git a/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf
new file mode 100644
index 0000000..eb52a79
Binary files /dev/null and b/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf differ
diff --git a/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff
new file mode 100644
index 0000000..f815f63
Binary files /dev/null and b/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff differ
diff --git a/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2
new file mode 100644
index 0000000..f2c76e5
Binary files /dev/null and b/singlehtml/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 differ
diff --git a/singlehtml/_static/jquery.js b/singlehtml/_static/jquery.js
new file mode 100644
index 0000000..c4c6022
--- /dev/null
+++ b/singlehtml/_static/jquery.js
@@ -0,0 +1,2 @@
+/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */
+!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML=" ",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML=" ";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML=" ",y.option=!!ce.lastChild;var ge={thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 "),n("table.docutils.footnote").wrap(""),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(' '),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t a.language.name.localeCompare(b.language.name));
+
+ const languagesHTML = `
+
+ Languages
+ ${languages
+ .map(
+ (translation) => `
+
+ ${translation.language.code}
+
+ `,
+ )
+ .join("\n")}
+
+ `;
+ return languagesHTML;
+ }
+
+ function renderVersions(config) {
+ if (!config.versions.active.length) {
+ return "";
+ }
+ const versionsHTML = `
+
+ Versions
+ ${config.versions.active
+ .map(
+ (version) => `
+
+ ${version.slug}
+
+ `,
+ )
+ .join("\n")}
+
+ `;
+ return versionsHTML;
+ }
+
+ function renderDownloads(config) {
+ if (!Object.keys(config.versions.current.downloads).length) {
+ return "";
+ }
+ const downloadsNameDisplay = {
+ pdf: "PDF",
+ epub: "Epub",
+ htmlzip: "HTML",
+ };
+
+ const downloadsHTML = `
+
+ Downloads
+ ${Object.entries(config.versions.current.downloads)
+ .map(
+ ([name, url]) => `
+
+ ${downloadsNameDisplay[name]}
+
+ `,
+ )
+ .join("\n")}
+
+ `;
+ return downloadsHTML;
+ }
+
+ document.addEventListener("readthedocs-addons-data-ready", function (event) {
+ const config = event.detail.data();
+
+ const flyout = `
+
+
+ Read the Docs
+ v: ${config.versions.current.slug}
+
+
+
+
+ ${renderLanguages(config)}
+ ${renderVersions(config)}
+ ${renderDownloads(config)}
+
+ On Read the Docs
+
+ Project Home
+
+
+ Builds
+
+
+ Downloads
+
+
+
+ Search
+
+
+
+
+
+
+ Hosted by Read the Docs
+
+
+
+ `;
+
+ // Inject the generated flyout into the body HTML element.
+ document.body.insertAdjacentHTML("beforeend", flyout);
+
+ // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout.
+ document
+ .querySelector("#flyout-search-form")
+ .addEventListener("focusin", () => {
+ const event = new CustomEvent("readthedocs-search-show");
+ document.dispatchEvent(event);
+ });
+ })
+}
+
+if (themeLanguageSelector || themeVersionSelector) {
+ function onSelectorSwitch(event) {
+ const option = event.target.selectedIndex;
+ const item = event.target.options[option];
+ window.location.href = item.dataset.url;
+ }
+
+ document.addEventListener("readthedocs-addons-data-ready", function (event) {
+ const config = event.detail.data();
+
+ const versionSwitch = document.querySelector(
+ "div.switch-menus > div.version-switch",
+ );
+ if (themeVersionSelector) {
+ let versions = config.versions.active;
+ if (config.versions.current.hidden || config.versions.current.type === "external") {
+ versions.unshift(config.versions.current);
+ }
+ const versionSelect = `
+
+ ${versions
+ .map(
+ (version) => `
+
+ ${version.slug}
+ `,
+ )
+ .join("\n")}
+
+ `;
+
+ versionSwitch.innerHTML = versionSelect;
+ versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch);
+ }
+
+ const languageSwitch = document.querySelector(
+ "div.switch-menus > div.language-switch",
+ );
+
+ if (themeLanguageSelector) {
+ if (config.projects.translations.length) {
+ // Add the current language to the options on the selector
+ let languages = config.projects.translations.concat(
+ config.projects.current,
+ );
+ languages = languages.sort((a, b) =>
+ a.language.name.localeCompare(b.language.name),
+ );
+
+ const languageSelect = `
+
+ ${languages
+ .map(
+ (language) => `
+
+ ${language.language.name}
+ `,
+ )
+ .join("\n")}
+
+ `;
+
+ languageSwitch.innerHTML = languageSelect;
+ languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch);
+ }
+ else {
+ languageSwitch.remove();
+ }
+ }
+ });
+}
+
+document.addEventListener("readthedocs-addons-data-ready", function (event) {
+ // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav.
+ document
+ .querySelector("[role='search'] input")
+ .addEventListener("focusin", () => {
+ const event = new CustomEvent("readthedocs-search-show");
+ document.dispatchEvent(event);
+ });
+});
\ No newline at end of file
diff --git a/singlehtml/_static/language_data.js b/singlehtml/_static/language_data.js
new file mode 100644
index 0000000..367b8ed
--- /dev/null
+++ b/singlehtml/_static/language_data.js
@@ -0,0 +1,199 @@
+/*
+ * language_data.js
+ * ~~~~~~~~~~~~~~~~
+ *
+ * This script contains the language-specific data used by searchtools.js,
+ * namely the list of stopwords, stemmer, scorer and splitter.
+ *
+ * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+
+var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"];
+
+
+/* Non-minified version is copied as a separate JS file, if available */
+
+/**
+ * Porter Stemmer
+ */
+var Stemmer = function() {
+
+ var step2list = {
+ ational: 'ate',
+ tional: 'tion',
+ enci: 'ence',
+ anci: 'ance',
+ izer: 'ize',
+ bli: 'ble',
+ alli: 'al',
+ entli: 'ent',
+ eli: 'e',
+ ousli: 'ous',
+ ization: 'ize',
+ ation: 'ate',
+ ator: 'ate',
+ alism: 'al',
+ iveness: 'ive',
+ fulness: 'ful',
+ ousness: 'ous',
+ aliti: 'al',
+ iviti: 'ive',
+ biliti: 'ble',
+ logi: 'log'
+ };
+
+ var step3list = {
+ icate: 'ic',
+ ative: '',
+ alize: 'al',
+ iciti: 'ic',
+ ical: 'ic',
+ ful: '',
+ ness: ''
+ };
+
+ var c = "[^aeiou]"; // consonant
+ var v = "[aeiouy]"; // vowel
+ var C = c + "[^aeiouy]*"; // consonant sequence
+ var V = v + "[aeiou]*"; // vowel sequence
+
+ var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
+ var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
+ var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
+ var s_v = "^(" + C + ")?" + v; // vowel in stem
+
+ this.stemWord = function (w) {
+ var stem;
+ var suffix;
+ var firstch;
+ var origword = w;
+
+ if (w.length < 3)
+ return w;
+
+ var re;
+ var re2;
+ var re3;
+ var re4;
+
+ firstch = w.substr(0,1);
+ if (firstch == "y")
+ w = firstch.toUpperCase() + w.substr(1);
+
+ // Step 1a
+ re = /^(.+?)(ss|i)es$/;
+ re2 = /^(.+?)([^s])s$/;
+
+ if (re.test(w))
+ w = w.replace(re,"$1$2");
+ else if (re2.test(w))
+ w = w.replace(re2,"$1$2");
+
+ // Step 1b
+ re = /^(.+?)eed$/;
+ re2 = /^(.+?)(ed|ing)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ re = new RegExp(mgr0);
+ if (re.test(fp[1])) {
+ re = /.$/;
+ w = w.replace(re,"");
+ }
+ }
+ else if (re2.test(w)) {
+ var fp = re2.exec(w);
+ stem = fp[1];
+ re2 = new RegExp(s_v);
+ if (re2.test(stem)) {
+ w = stem;
+ re2 = /(at|bl|iz)$/;
+ re3 = new RegExp("([^aeiouylsz])\\1$");
+ re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
+ if (re2.test(w))
+ w = w + "e";
+ else if (re3.test(w)) {
+ re = /.$/;
+ w = w.replace(re,"");
+ }
+ else if (re4.test(w))
+ w = w + "e";
+ }
+ }
+
+ // Step 1c
+ re = /^(.+?)y$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = new RegExp(s_v);
+ if (re.test(stem))
+ w = stem + "i";
+ }
+
+ // Step 2
+ re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ suffix = fp[2];
+ re = new RegExp(mgr0);
+ if (re.test(stem))
+ w = stem + step2list[suffix];
+ }
+
+ // Step 3
+ re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ suffix = fp[2];
+ re = new RegExp(mgr0);
+ if (re.test(stem))
+ w = stem + step3list[suffix];
+ }
+
+ // Step 4
+ re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
+ re2 = /^(.+?)(s|t)(ion)$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = new RegExp(mgr1);
+ if (re.test(stem))
+ w = stem;
+ }
+ else if (re2.test(w)) {
+ var fp = re2.exec(w);
+ stem = fp[1] + fp[2];
+ re2 = new RegExp(mgr1);
+ if (re2.test(stem))
+ w = stem;
+ }
+
+ // Step 5
+ re = /^(.+?)e$/;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = new RegExp(mgr1);
+ re2 = new RegExp(meq1);
+ re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
+ if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
+ w = stem;
+ }
+ re = /ll$/;
+ re2 = new RegExp(mgr1);
+ if (re.test(w) && re2.test(w)) {
+ re = /.$/;
+ w = w.replace(re,"");
+ }
+
+ // and turn initial Y back to y
+ if (firstch == "y")
+ w = firstch.toLowerCase() + w.substr(1);
+ return w;
+ }
+}
+
diff --git a/singlehtml/_static/minipres.js b/singlehtml/_static/minipres.js
new file mode 100644
index 0000000..ad11c87
--- /dev/null
+++ b/singlehtml/_static/minipres.js
@@ -0,0 +1,223 @@
+// Add goTo method to elements
+// http://stackoverflow.com/questions/4801655/how-to-go-to-a-specific-element-on-page
+(function($) {
+ $.fn.goTo = function() {
+ $('html, body').animate({
+ scrollTop: $(this).offset().top //+ 'px'
+ }, 'fast');
+ return this; // for chaining...
+ }
+})(jQuery);
+
+// NO good way to do this!. Copy a hack from here
+// https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
+// https://stackoverflow.com/a/2880929
+var urlParams;
+(window.onpopstate = function () {
+ var match,
+ pl = /\+/g, // Regex for replacing addition symbol with a space
+ search = /([^&=]+)=?([^&]*)/g,
+ decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
+ query = window.location.search.substring(1);
+ urlParams = {};
+ while (match = search.exec(query))
+ urlParams[decode(match[1])] = decode(match[2]);
+})();
+
+// Select heading levels
+var maxHeading = urlParams['h']
+if (maxHeading === undefined) maxHeading = 2
+var headingLevels = [];
+for (h=2 ; h
(sections.length-1) ) {
+ // if we would scroll past bottom, or above top, do nothing
+ return;
+ }
+
+ console.log('xxxxxx');
+ var targetSection = sections[targetPos];
+ console.log(targetSection, typeof(targetSection));
+
+ // Return targetSection top and height
+ var secProperties = section_top_and_height(targetSection);
+ var top = secProperties['top'];
+ var height = secProperties['height']
+ var win_height = window.innerHeight;
+ //console.info(top, height, win_height)
+
+ var scroll_to = 0;
+ if (height >= win_height || height == 0) {
+ scroll_to = top;
+ } else {
+ scroll_to = top - (win_height-height)/3.;
+ }
+ //console.info(top, height, win_height, scroll_to)
+
+ $('html, body').animate({
+ scrollTop: scroll_to //+ 'px'
+ }, 'fast');
+
+}
+
+
+function minipres() {
+ /* Enable the minipres mode:
+ - call the hide() function
+ - set up the scrolling listener
+ */
+ document.addEventListener('keydown', function (event) {
+ switch(event.which) {
+ case 37: // left
+ switch_slide(-1);
+ event.preventDefault();
+ return false;
+ break;
+ //case 38: // up
+ case 39: // right
+ switch_slide(+1);
+ event.preventDefault();
+ return false;
+ break;
+ //case 40: // down
+ default:
+ return; // exit this handler for other keys
+ }
+ }, true)
+
+ hide()
+
+ // Increase space between sections
+ //$("div .section").css('margin-bottom', '50%');
+ $(sectionSelector).css('margin-top', '50%');
+
+ // Reduce size/color of other sections
+ if (hiddenSectionSelector.length > 0) {
+ var hideNodes = $(hiddenSectionSelector);
+ console.log(typeof hideNodes, hideNodes);
+ for (node in hideNodes) {
+ console.log("a", typeof node, node);
+ node = hideNodes[node]; // what's right way to iterate values?
+ console.log("b", typeof node, node);
+ if (node.parentNode && node.parentNode.className == "section") {
+ node = node.parentNode;
+ console.log("c", typeof node, node);
+ //node.css['transform'] = 'scale(.5)';
+ //node.css['transform-origin'] = 'top center';
+ $(node).css('color', 'lightgrey');
+ //$(node).css('font-size', '20%');
+ //$(node).css('visibility', 'collapse');
+ //ntahousnatouhasno;
+ }
+ }
+ }
+}
+
+function hide() {
+ /* Hide all non-essential elements on the page
+ */
+
+ // This is for sphinx_rst_theme and readthedocs
+ $(".wy-nav-side").remove();
+ $(".wy-nav-content-wrap").css('margin-left', 0);
+ $('.rst-versions').remove(); // readthedocs version selector
+
+ // Add other formats here.
+}
+
+
+var slideshow = minipres;
+
+if (window.location.search.match(/[?&](minipres|slideshow|pres)([=&]|$)/) ) {
+ //minipres()
+ window.addEventListener("load", minipres);
+} else if (window.location.search.match(/[?&](plain)([=&]|$)/) ) {
+ window.addEventListener("load", hide);
+}
diff --git a/singlehtml/_static/minus.png b/singlehtml/_static/minus.png
new file mode 100644
index 0000000..d96755f
Binary files /dev/null and b/singlehtml/_static/minus.png differ
diff --git a/singlehtml/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css b/singlehtml/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css
new file mode 100644
index 0000000..3356631
--- /dev/null
+++ b/singlehtml/_static/mystnb.4510f1fc1dee50b3e5859aac5469c37c29e427902b24a333a5f9fcb2f0b3ac41.css
@@ -0,0 +1,2342 @@
+/* Variables */
+:root {
+ --mystnb-source-bg-color: #f7f7f7;
+ --mystnb-stdout-bg-color: #fcfcfc;
+ --mystnb-stderr-bg-color: #fdd;
+ --mystnb-traceback-bg-color: #fcfcfc;
+ --mystnb-source-border-color: #ccc;
+ --mystnb-source-margin-color: green;
+ --mystnb-stdout-border-color: #f7f7f7;
+ --mystnb-stderr-border-color: #f7f7f7;
+ --mystnb-traceback-border-color: #ffd6d6;
+ --mystnb-hide-prompt-opacity: 70%;
+ --mystnb-source-border-radius: .4em;
+ --mystnb-source-border-width: 1px;
+}
+
+/* Whole cell */
+div.container.cell {
+ padding-left: 0;
+ margin-bottom: 1em;
+}
+
+/* Removing all background formatting so we can control at the div level */
+.cell_input div.highlight,
+.cell_output pre,
+.cell_input pre,
+.cell_output .output {
+ border: none;
+ box-shadow: none;
+}
+
+.cell_output .output pre,
+.cell_input pre {
+ margin: 0px;
+}
+
+/* Input cells */
+div.cell div.cell_input,
+div.cell details.above-input>summary {
+ padding-left: 0em;
+ padding-right: 0em;
+ border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid;
+ background-color: var(--mystnb-source-bg-color);
+ border-left-color: var(--mystnb-source-margin-color);
+ border-left-width: medium;
+ border-radius: var(--mystnb-source-border-radius);
+}
+
+div.cell_input>div,
+div.cell_output div.output>div.highlight {
+ margin: 0em !important;
+ border: none !important;
+}
+
+/* All cell outputs */
+.cell_output {
+ padding-left: 1em;
+ padding-right: 0em;
+ margin-top: 1em;
+}
+
+/* Text outputs from cells */
+.cell_output .output.text_plain,
+.cell_output .output.traceback,
+.cell_output .output.stream,
+.cell_output .output.stderr {
+ margin-top: 1em;
+ margin-bottom: 0em;
+ box-shadow: none;
+}
+
+.cell_output .output.text_plain,
+.cell_output .output.stream {
+ background: var(--mystnb-stdout-bg-color);
+ border: 1px solid var(--mystnb-stdout-border-color);
+}
+
+.cell_output .output.stderr {
+ background: var(--mystnb-stderr-bg-color);
+ border: 1px solid var(--mystnb-stderr-border-color);
+}
+
+.cell_output .output.traceback {
+ background: var(--mystnb-traceback-bg-color);
+ border: 1px solid var(--mystnb-traceback-border-color);
+}
+
+/* Collapsible cell content */
+div.cell details.above-input div.cell_input {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-top: var(--mystnb-source-border-width) var(--mystnb-source-border-color) dashed;
+}
+
+div.cell div.cell_input.above-output-prompt {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+div.cell details.above-input>summary {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ border-bottom: var(--mystnb-source-border-width) var(--mystnb-source-border-color) dashed;
+ padding-left: 1em;
+ margin-bottom: 0;
+}
+
+div.cell details.above-output>summary {
+ background-color: var(--mystnb-source-bg-color);
+ padding-left: 1em;
+ padding-right: 0em;
+ border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid;
+ border-radius: var(--mystnb-source-border-radius);
+ border-left-color: var(--mystnb-source-margin-color);
+ border-left-width: medium;
+}
+
+div.cell details.below-input>summary {
+ background-color: var(--mystnb-source-bg-color);
+ padding-left: 1em;
+ padding-right: 0em;
+ border: var(--mystnb-source-border-width) var(--mystnb-source-border-color) solid;
+ border-top: none;
+ border-bottom-left-radius: var(--mystnb-source-border-radius);
+ border-bottom-right-radius: var(--mystnb-source-border-radius);
+ border-left-color: var(--mystnb-source-margin-color);
+ border-left-width: medium;
+}
+
+div.cell details.hide>summary>span {
+ opacity: var(--mystnb-hide-prompt-opacity);
+}
+
+div.cell details.hide[open]>summary>span.collapsed {
+ display: none;
+}
+
+div.cell details.hide:not([open])>summary>span.expanded {
+ display: none;
+}
+
+@keyframes collapsed-fade-in {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
+div.cell details.hide[open]>summary~* {
+ -moz-animation: collapsed-fade-in 0.3s ease-in-out;
+ -webkit-animation: collapsed-fade-in 0.3s ease-in-out;
+ animation: collapsed-fade-in 0.3s ease-in-out;
+}
+
+/* Math align to the left */
+.cell_output .MathJax_Display {
+ text-align: left !important;
+}
+
+/* Pandas tables. Pulled from the Jupyter / nbsphinx CSS */
+div.cell_output table {
+ border: none;
+ border-collapse: collapse;
+ border-spacing: 0;
+ color: black;
+ font-size: 1em;
+ table-layout: fixed;
+}
+
+div.cell_output thead {
+ border-bottom: 1px solid black;
+ vertical-align: bottom;
+}
+
+div.cell_output tr,
+div.cell_output th,
+div.cell_output td {
+ text-align: right;
+ vertical-align: middle;
+ padding: 0.5em 0.5em;
+ line-height: normal;
+ white-space: normal;
+ max-width: none;
+ border: none;
+}
+
+div.cell_output th {
+ font-weight: bold;
+}
+
+div.cell_output tbody tr:nth-child(odd) {
+ background: #f5f5f5;
+}
+
+div.cell_output tbody tr:hover {
+ background: rgba(66, 165, 245, 0.2);
+}
+
+/** source code line numbers **/
+span.linenos {
+ opacity: 0.5;
+}
+
+/* Inline text from `paste` operation */
+
+span.pasted-text {
+ font-weight: bold;
+}
+
+span.pasted-inline img {
+ max-height: 2em;
+}
+
+tbody span.pasted-inline img {
+ max-height: none;
+}
+
+/* Font colors for translated ANSI escape sequences
+Color values are copied from Jupyter Notebook
+https://github.com/jupyter/notebook/blob/52581f8eda9b319eb0390ac77fe5903c38f81e3e/notebook/static/notebook/less/ansicolors.less#L14-L21
+Background colors from
+https://nbsphinx.readthedocs.io/en/latest/code-cells.html#ANSI-Colors
+*/
+div.highlight .-Color-Bold {
+ font-weight: bold;
+}
+
+div.highlight .-Color[class*=-Black] {
+ color: #3E424D
+}
+
+div.highlight .-Color[class*=-Red] {
+ color: #E75C58
+}
+
+div.highlight .-Color[class*=-Green] {
+ color: #00A250
+}
+
+div.highlight .-Color[class*=-Yellow] {
+ color: #DDB62B
+}
+
+div.highlight .-Color[class*=-Blue] {
+ color: #208FFB
+}
+
+div.highlight .-Color[class*=-Magenta] {
+ color: #D160C4
+}
+
+div.highlight .-Color[class*=-Cyan] {
+ color: #60C6C8
+}
+
+div.highlight .-Color[class*=-White] {
+ color: #C5C1B4
+}
+
+div.highlight .-Color[class*=-BGBlack] {
+ background-color: #3E424D
+}
+
+div.highlight .-Color[class*=-BGRed] {
+ background-color: #E75C58
+}
+
+div.highlight .-Color[class*=-BGGreen] {
+ background-color: #00A250
+}
+
+div.highlight .-Color[class*=-BGYellow] {
+ background-color: #DDB62B
+}
+
+div.highlight .-Color[class*=-BGBlue] {
+ background-color: #208FFB
+}
+
+div.highlight .-Color[class*=-BGMagenta] {
+ background-color: #D160C4
+}
+
+div.highlight .-Color[class*=-BGCyan] {
+ background-color: #60C6C8
+}
+
+div.highlight .-Color[class*=-BGWhite] {
+ background-color: #C5C1B4
+}
+
+/* Font colors for 8-bit ANSI */
+
+div.highlight .-Color[class*=-C0] {
+ color: #000000
+}
+
+div.highlight .-Color[class*=-BGC0] {
+ background-color: #000000
+}
+
+div.highlight .-Color[class*=-C1] {
+ color: #800000
+}
+
+div.highlight .-Color[class*=-BGC1] {
+ background-color: #800000
+}
+
+div.highlight .-Color[class*=-C2] {
+ color: #008000
+}
+
+div.highlight .-Color[class*=-BGC2] {
+ background-color: #008000
+}
+
+div.highlight .-Color[class*=-C3] {
+ color: #808000
+}
+
+div.highlight .-Color[class*=-BGC3] {
+ background-color: #808000
+}
+
+div.highlight .-Color[class*=-C4] {
+ color: #000080
+}
+
+div.highlight .-Color[class*=-BGC4] {
+ background-color: #000080
+}
+
+div.highlight .-Color[class*=-C5] {
+ color: #800080
+}
+
+div.highlight .-Color[class*=-BGC5] {
+ background-color: #800080
+}
+
+div.highlight .-Color[class*=-C6] {
+ color: #008080
+}
+
+div.highlight .-Color[class*=-BGC6] {
+ background-color: #008080
+}
+
+div.highlight .-Color[class*=-C7] {
+ color: #C0C0C0
+}
+
+div.highlight .-Color[class*=-BGC7] {
+ background-color: #C0C0C0
+}
+
+div.highlight .-Color[class*=-C8] {
+ color: #808080
+}
+
+div.highlight .-Color[class*=-BGC8] {
+ background-color: #808080
+}
+
+div.highlight .-Color[class*=-C9] {
+ color: #FF0000
+}
+
+div.highlight .-Color[class*=-BGC9] {
+ background-color: #FF0000
+}
+
+div.highlight .-Color[class*=-C10] {
+ color: #00FF00
+}
+
+div.highlight .-Color[class*=-BGC10] {
+ background-color: #00FF00
+}
+
+div.highlight .-Color[class*=-C11] {
+ color: #FFFF00
+}
+
+div.highlight .-Color[class*=-BGC11] {
+ background-color: #FFFF00
+}
+
+div.highlight .-Color[class*=-C12] {
+ color: #0000FF
+}
+
+div.highlight .-Color[class*=-BGC12] {
+ background-color: #0000FF
+}
+
+div.highlight .-Color[class*=-C13] {
+ color: #FF00FF
+}
+
+div.highlight .-Color[class*=-BGC13] {
+ background-color: #FF00FF
+}
+
+div.highlight .-Color[class*=-C14] {
+ color: #00FFFF
+}
+
+div.highlight .-Color[class*=-BGC14] {
+ background-color: #00FFFF
+}
+
+div.highlight .-Color[class*=-C15] {
+ color: #FFFFFF
+}
+
+div.highlight .-Color[class*=-BGC15] {
+ background-color: #FFFFFF
+}
+
+div.highlight .-Color[class*=-C16] {
+ color: #000000
+}
+
+div.highlight .-Color[class*=-BGC16] {
+ background-color: #000000
+}
+
+div.highlight .-Color[class*=-C17] {
+ color: #00005F
+}
+
+div.highlight .-Color[class*=-BGC17] {
+ background-color: #00005F
+}
+
+div.highlight .-Color[class*=-C18] {
+ color: #000087
+}
+
+div.highlight .-Color[class*=-BGC18] {
+ background-color: #000087
+}
+
+div.highlight .-Color[class*=-C19] {
+ color: #0000AF
+}
+
+div.highlight .-Color[class*=-BGC19] {
+ background-color: #0000AF
+}
+
+div.highlight .-Color[class*=-C20] {
+ color: #0000D7
+}
+
+div.highlight .-Color[class*=-BGC20] {
+ background-color: #0000D7
+}
+
+div.highlight .-Color[class*=-C21] {
+ color: #0000FF
+}
+
+div.highlight .-Color[class*=-BGC21] {
+ background-color: #0000FF
+}
+
+div.highlight .-Color[class*=-C22] {
+ color: #005F00
+}
+
+div.highlight .-Color[class*=-BGC22] {
+ background-color: #005F00
+}
+
+div.highlight .-Color[class*=-C23] {
+ color: #005F5F
+}
+
+div.highlight .-Color[class*=-BGC23] {
+ background-color: #005F5F
+}
+
+div.highlight .-Color[class*=-C24] {
+ color: #005F87
+}
+
+div.highlight .-Color[class*=-BGC24] {
+ background-color: #005F87
+}
+
+div.highlight .-Color[class*=-C25] {
+ color: #005FAF
+}
+
+div.highlight .-Color[class*=-BGC25] {
+ background-color: #005FAF
+}
+
+div.highlight .-Color[class*=-C26] {
+ color: #005FD7
+}
+
+div.highlight .-Color[class*=-BGC26] {
+ background-color: #005FD7
+}
+
+div.highlight .-Color[class*=-C27] {
+ color: #005FFF
+}
+
+div.highlight .-Color[class*=-BGC27] {
+ background-color: #005FFF
+}
+
+div.highlight .-Color[class*=-C28] {
+ color: #008700
+}
+
+div.highlight .-Color[class*=-BGC28] {
+ background-color: #008700
+}
+
+div.highlight .-Color[class*=-C29] {
+ color: #00875F
+}
+
+div.highlight .-Color[class*=-BGC29] {
+ background-color: #00875F
+}
+
+div.highlight .-Color[class*=-C30] {
+ color: #008787
+}
+
+div.highlight .-Color[class*=-BGC30] {
+ background-color: #008787
+}
+
+div.highlight .-Color[class*=-C31] {
+ color: #0087AF
+}
+
+div.highlight .-Color[class*=-BGC31] {
+ background-color: #0087AF
+}
+
+div.highlight .-Color[class*=-C32] {
+ color: #0087D7
+}
+
+div.highlight .-Color[class*=-BGC32] {
+ background-color: #0087D7
+}
+
+div.highlight .-Color[class*=-C33] {
+ color: #0087FF
+}
+
+div.highlight .-Color[class*=-BGC33] {
+ background-color: #0087FF
+}
+
+div.highlight .-Color[class*=-C34] {
+ color: #00AF00
+}
+
+div.highlight .-Color[class*=-BGC34] {
+ background-color: #00AF00
+}
+
+div.highlight .-Color[class*=-C35] {
+ color: #00AF5F
+}
+
+div.highlight .-Color[class*=-BGC35] {
+ background-color: #00AF5F
+}
+
+div.highlight .-Color[class*=-C36] {
+ color: #00AF87
+}
+
+div.highlight .-Color[class*=-BGC36] {
+ background-color: #00AF87
+}
+
+div.highlight .-Color[class*=-C37] {
+ color: #00AFAF
+}
+
+div.highlight .-Color[class*=-BGC37] {
+ background-color: #00AFAF
+}
+
+div.highlight .-Color[class*=-C38] {
+ color: #00AFD7
+}
+
+div.highlight .-Color[class*=-BGC38] {
+ background-color: #00AFD7
+}
+
+div.highlight .-Color[class*=-C39] {
+ color: #00AFFF
+}
+
+div.highlight .-Color[class*=-BGC39] {
+ background-color: #00AFFF
+}
+
+div.highlight .-Color[class*=-C40] {
+ color: #00D700
+}
+
+div.highlight .-Color[class*=-BGC40] {
+ background-color: #00D700
+}
+
+div.highlight .-Color[class*=-C41] {
+ color: #00D75F
+}
+
+div.highlight .-Color[class*=-BGC41] {
+ background-color: #00D75F
+}
+
+div.highlight .-Color[class*=-C42] {
+ color: #00D787
+}
+
+div.highlight .-Color[class*=-BGC42] {
+ background-color: #00D787
+}
+
+div.highlight .-Color[class*=-C43] {
+ color: #00D7AF
+}
+
+div.highlight .-Color[class*=-BGC43] {
+ background-color: #00D7AF
+}
+
+div.highlight .-Color[class*=-C44] {
+ color: #00D7D7
+}
+
+div.highlight .-Color[class*=-BGC44] {
+ background-color: #00D7D7
+}
+
+div.highlight .-Color[class*=-C45] {
+ color: #00D7FF
+}
+
+div.highlight .-Color[class*=-BGC45] {
+ background-color: #00D7FF
+}
+
+div.highlight .-Color[class*=-C46] {
+ color: #00FF00
+}
+
+div.highlight .-Color[class*=-BGC46] {
+ background-color: #00FF00
+}
+
+div.highlight .-Color[class*=-C47] {
+ color: #00FF5F
+}
+
+div.highlight .-Color[class*=-BGC47] {
+ background-color: #00FF5F
+}
+
+div.highlight .-Color[class*=-C48] {
+ color: #00FF87
+}
+
+div.highlight .-Color[class*=-BGC48] {
+ background-color: #00FF87
+}
+
+div.highlight .-Color[class*=-C49] {
+ color: #00FFAF
+}
+
+div.highlight .-Color[class*=-BGC49] {
+ background-color: #00FFAF
+}
+
+div.highlight .-Color[class*=-C50] {
+ color: #00FFD7
+}
+
+div.highlight .-Color[class*=-BGC50] {
+ background-color: #00FFD7
+}
+
+div.highlight .-Color[class*=-C51] {
+ color: #00FFFF
+}
+
+div.highlight .-Color[class*=-BGC51] {
+ background-color: #00FFFF
+}
+
+div.highlight .-Color[class*=-C52] {
+ color: #5F0000
+}
+
+div.highlight .-Color[class*=-BGC52] {
+ background-color: #5F0000
+}
+
+div.highlight .-Color[class*=-C53] {
+ color: #5F005F
+}
+
+div.highlight .-Color[class*=-BGC53] {
+ background-color: #5F005F
+}
+
+div.highlight .-Color[class*=-C54] {
+ color: #5F0087
+}
+
+div.highlight .-Color[class*=-BGC54] {
+ background-color: #5F0087
+}
+
+div.highlight .-Color[class*=-C55] {
+ color: #5F00AF
+}
+
+div.highlight .-Color[class*=-BGC55] {
+ background-color: #5F00AF
+}
+
+div.highlight .-Color[class*=-C56] {
+ color: #5F00D7
+}
+
+div.highlight .-Color[class*=-BGC56] {
+ background-color: #5F00D7
+}
+
+div.highlight .-Color[class*=-C57] {
+ color: #5F00FF
+}
+
+div.highlight .-Color[class*=-BGC57] {
+ background-color: #5F00FF
+}
+
+div.highlight .-Color[class*=-C58] {
+ color: #5F5F00
+}
+
+div.highlight .-Color[class*=-BGC58] {
+ background-color: #5F5F00
+}
+
+div.highlight .-Color[class*=-C59] {
+ color: #5F5F5F
+}
+
+div.highlight .-Color[class*=-BGC59] {
+ background-color: #5F5F5F
+}
+
+div.highlight .-Color[class*=-C60] {
+ color: #5F5F87
+}
+
+div.highlight .-Color[class*=-BGC60] {
+ background-color: #5F5F87
+}
+
+div.highlight .-Color[class*=-C61] {
+ color: #5F5FAF
+}
+
+div.highlight .-Color[class*=-BGC61] {
+ background-color: #5F5FAF
+}
+
+div.highlight .-Color[class*=-C62] {
+ color: #5F5FD7
+}
+
+div.highlight .-Color[class*=-BGC62] {
+ background-color: #5F5FD7
+}
+
+div.highlight .-Color[class*=-C63] {
+ color: #5F5FFF
+}
+
+div.highlight .-Color[class*=-BGC63] {
+ background-color: #5F5FFF
+}
+
+div.highlight .-Color[class*=-C64] {
+ color: #5F8700
+}
+
+div.highlight .-Color[class*=-BGC64] {
+ background-color: #5F8700
+}
+
+div.highlight .-Color[class*=-C65] {
+ color: #5F875F
+}
+
+div.highlight .-Color[class*=-BGC65] {
+ background-color: #5F875F
+}
+
+div.highlight .-Color[class*=-C66] {
+ color: #5F8787
+}
+
+div.highlight .-Color[class*=-BGC66] {
+ background-color: #5F8787
+}
+
+div.highlight .-Color[class*=-C67] {
+ color: #5F87AF
+}
+
+div.highlight .-Color[class*=-BGC67] {
+ background-color: #5F87AF
+}
+
+div.highlight .-Color[class*=-C68] {
+ color: #5F87D7
+}
+
+div.highlight .-Color[class*=-BGC68] {
+ background-color: #5F87D7
+}
+
+div.highlight .-Color[class*=-C69] {
+ color: #5F87FF
+}
+
+div.highlight .-Color[class*=-BGC69] {
+ background-color: #5F87FF
+}
+
+div.highlight .-Color[class*=-C70] {
+ color: #5FAF00
+}
+
+div.highlight .-Color[class*=-BGC70] {
+ background-color: #5FAF00
+}
+
+div.highlight .-Color[class*=-C71] {
+ color: #5FAF5F
+}
+
+div.highlight .-Color[class*=-BGC71] {
+ background-color: #5FAF5F
+}
+
+div.highlight .-Color[class*=-C72] {
+ color: #5FAF87
+}
+
+div.highlight .-Color[class*=-BGC72] {
+ background-color: #5FAF87
+}
+
+div.highlight .-Color[class*=-C73] {
+ color: #5FAFAF
+}
+
+div.highlight .-Color[class*=-BGC73] {
+ background-color: #5FAFAF
+}
+
+div.highlight .-Color[class*=-C74] {
+ color: #5FAFD7
+}
+
+div.highlight .-Color[class*=-BGC74] {
+ background-color: #5FAFD7
+}
+
+div.highlight .-Color[class*=-C75] {
+ color: #5FAFFF
+}
+
+div.highlight .-Color[class*=-BGC75] {
+ background-color: #5FAFFF
+}
+
+div.highlight .-Color[class*=-C76] {
+ color: #5FD700
+}
+
+div.highlight .-Color[class*=-BGC76] {
+ background-color: #5FD700
+}
+
+div.highlight .-Color[class*=-C77] {
+ color: #5FD75F
+}
+
+div.highlight .-Color[class*=-BGC77] {
+ background-color: #5FD75F
+}
+
+div.highlight .-Color[class*=-C78] {
+ color: #5FD787
+}
+
+div.highlight .-Color[class*=-BGC78] {
+ background-color: #5FD787
+}
+
+div.highlight .-Color[class*=-C79] {
+ color: #5FD7AF
+}
+
+div.highlight .-Color[class*=-BGC79] {
+ background-color: #5FD7AF
+}
+
+div.highlight .-Color[class*=-C80] {
+ color: #5FD7D7
+}
+
+div.highlight .-Color[class*=-BGC80] {
+ background-color: #5FD7D7
+}
+
+div.highlight .-Color[class*=-C81] {
+ color: #5FD7FF
+}
+
+div.highlight .-Color[class*=-BGC81] {
+ background-color: #5FD7FF
+}
+
+div.highlight .-Color[class*=-C82] {
+ color: #5FFF00
+}
+
+div.highlight .-Color[class*=-BGC82] {
+ background-color: #5FFF00
+}
+
+div.highlight .-Color[class*=-C83] {
+ color: #5FFF5F
+}
+
+div.highlight .-Color[class*=-BGC83] {
+ background-color: #5FFF5F
+}
+
+div.highlight .-Color[class*=-C84] {
+ color: #5FFF87
+}
+
+div.highlight .-Color[class*=-BGC84] {
+ background-color: #5FFF87
+}
+
+div.highlight .-Color[class*=-C85] {
+ color: #5FFFAF
+}
+
+div.highlight .-Color[class*=-BGC85] {
+ background-color: #5FFFAF
+}
+
+div.highlight .-Color[class*=-C86] {
+ color: #5FFFD7
+}
+
+div.highlight .-Color[class*=-BGC86] {
+ background-color: #5FFFD7
+}
+
+div.highlight .-Color[class*=-C87] {
+ color: #5FFFFF
+}
+
+div.highlight .-Color[class*=-BGC87] {
+ background-color: #5FFFFF
+}
+
+div.highlight .-Color[class*=-C88] {
+ color: #870000
+}
+
+div.highlight .-Color[class*=-BGC88] {
+ background-color: #870000
+}
+
+div.highlight .-Color[class*=-C89] {
+ color: #87005F
+}
+
+div.highlight .-Color[class*=-BGC89] {
+ background-color: #87005F
+}
+
+div.highlight .-Color[class*=-C90] {
+ color: #870087
+}
+
+div.highlight .-Color[class*=-BGC90] {
+ background-color: #870087
+}
+
+div.highlight .-Color[class*=-C91] {
+ color: #8700AF
+}
+
+div.highlight .-Color[class*=-BGC91] {
+ background-color: #8700AF
+}
+
+div.highlight .-Color[class*=-C92] {
+ color: #8700D7
+}
+
+div.highlight .-Color[class*=-BGC92] {
+ background-color: #8700D7
+}
+
+div.highlight .-Color[class*=-C93] {
+ color: #8700FF
+}
+
+div.highlight .-Color[class*=-BGC93] {
+ background-color: #8700FF
+}
+
+div.highlight .-Color[class*=-C94] {
+ color: #875F00
+}
+
+div.highlight .-Color[class*=-BGC94] {
+ background-color: #875F00
+}
+
+div.highlight .-Color[class*=-C95] {
+ color: #875F5F
+}
+
+div.highlight .-Color[class*=-BGC95] {
+ background-color: #875F5F
+}
+
+div.highlight .-Color[class*=-C96] {
+ color: #875F87
+}
+
+div.highlight .-Color[class*=-BGC96] {
+ background-color: #875F87
+}
+
+div.highlight .-Color[class*=-C97] {
+ color: #875FAF
+}
+
+div.highlight .-Color[class*=-BGC97] {
+ background-color: #875FAF
+}
+
+div.highlight .-Color[class*=-C98] {
+ color: #875FD7
+}
+
+div.highlight .-Color[class*=-BGC98] {
+ background-color: #875FD7
+}
+
+div.highlight .-Color[class*=-C99] {
+ color: #875FFF
+}
+
+div.highlight .-Color[class*=-BGC99] {
+ background-color: #875FFF
+}
+
+div.highlight .-Color[class*=-C100] {
+ color: #878700
+}
+
+div.highlight .-Color[class*=-BGC100] {
+ background-color: #878700
+}
+
+div.highlight .-Color[class*=-C101] {
+ color: #87875F
+}
+
+div.highlight .-Color[class*=-BGC101] {
+ background-color: #87875F
+}
+
+div.highlight .-Color[class*=-C102] {
+ color: #878787
+}
+
+div.highlight .-Color[class*=-BGC102] {
+ background-color: #878787
+}
+
+div.highlight .-Color[class*=-C103] {
+ color: #8787AF
+}
+
+div.highlight .-Color[class*=-BGC103] {
+ background-color: #8787AF
+}
+
+div.highlight .-Color[class*=-C104] {
+ color: #8787D7
+}
+
+div.highlight .-Color[class*=-BGC104] {
+ background-color: #8787D7
+}
+
+div.highlight .-Color[class*=-C105] {
+ color: #8787FF
+}
+
+div.highlight .-Color[class*=-BGC105] {
+ background-color: #8787FF
+}
+
+div.highlight .-Color[class*=-C106] {
+ color: #87AF00
+}
+
+div.highlight .-Color[class*=-BGC106] {
+ background-color: #87AF00
+}
+
+div.highlight .-Color[class*=-C107] {
+ color: #87AF5F
+}
+
+div.highlight .-Color[class*=-BGC107] {
+ background-color: #87AF5F
+}
+
+div.highlight .-Color[class*=-C108] {
+ color: #87AF87
+}
+
+div.highlight .-Color[class*=-BGC108] {
+ background-color: #87AF87
+}
+
+div.highlight .-Color[class*=-C109] {
+ color: #87AFAF
+}
+
+div.highlight .-Color[class*=-BGC109] {
+ background-color: #87AFAF
+}
+
+div.highlight .-Color[class*=-C110] {
+ color: #87AFD7
+}
+
+div.highlight .-Color[class*=-BGC110] {
+ background-color: #87AFD7
+}
+
+div.highlight .-Color[class*=-C111] {
+ color: #87AFFF
+}
+
+div.highlight .-Color[class*=-BGC111] {
+ background-color: #87AFFF
+}
+
+div.highlight .-Color[class*=-C112] {
+ color: #87D700
+}
+
+div.highlight .-Color[class*=-BGC112] {
+ background-color: #87D700
+}
+
+div.highlight .-Color[class*=-C113] {
+ color: #87D75F
+}
+
+div.highlight .-Color[class*=-BGC113] {
+ background-color: #87D75F
+}
+
+div.highlight .-Color[class*=-C114] {
+ color: #87D787
+}
+
+div.highlight .-Color[class*=-BGC114] {
+ background-color: #87D787
+}
+
+div.highlight .-Color[class*=-C115] {
+ color: #87D7AF
+}
+
+div.highlight .-Color[class*=-BGC115] {
+ background-color: #87D7AF
+}
+
+div.highlight .-Color[class*=-C116] {
+ color: #87D7D7
+}
+
+div.highlight .-Color[class*=-BGC116] {
+ background-color: #87D7D7
+}
+
+div.highlight .-Color[class*=-C117] {
+ color: #87D7FF
+}
+
+div.highlight .-Color[class*=-BGC117] {
+ background-color: #87D7FF
+}
+
+div.highlight .-Color[class*=-C118] {
+ color: #87FF00
+}
+
+div.highlight .-Color[class*=-BGC118] {
+ background-color: #87FF00
+}
+
+div.highlight .-Color[class*=-C119] {
+ color: #87FF5F
+}
+
+div.highlight .-Color[class*=-BGC119] {
+ background-color: #87FF5F
+}
+
+div.highlight .-Color[class*=-C120] {
+ color: #87FF87
+}
+
+div.highlight .-Color[class*=-BGC120] {
+ background-color: #87FF87
+}
+
+div.highlight .-Color[class*=-C121] {
+ color: #87FFAF
+}
+
+div.highlight .-Color[class*=-BGC121] {
+ background-color: #87FFAF
+}
+
+div.highlight .-Color[class*=-C122] {
+ color: #87FFD7
+}
+
+div.highlight .-Color[class*=-BGC122] {
+ background-color: #87FFD7
+}
+
+div.highlight .-Color[class*=-C123] {
+ color: #87FFFF
+}
+
+div.highlight .-Color[class*=-BGC123] {
+ background-color: #87FFFF
+}
+
+div.highlight .-Color[class*=-C124] {
+ color: #AF0000
+}
+
+div.highlight .-Color[class*=-BGC124] {
+ background-color: #AF0000
+}
+
+div.highlight .-Color[class*=-C125] {
+ color: #AF005F
+}
+
+div.highlight .-Color[class*=-BGC125] {
+ background-color: #AF005F
+}
+
+div.highlight .-Color[class*=-C126] {
+ color: #AF0087
+}
+
+div.highlight .-Color[class*=-BGC126] {
+ background-color: #AF0087
+}
+
+div.highlight .-Color[class*=-C127] {
+ color: #AF00AF
+}
+
+div.highlight .-Color[class*=-BGC127] {
+ background-color: #AF00AF
+}
+
+div.highlight .-Color[class*=-C128] {
+ color: #AF00D7
+}
+
+div.highlight .-Color[class*=-BGC128] {
+ background-color: #AF00D7
+}
+
+div.highlight .-Color[class*=-C129] {
+ color: #AF00FF
+}
+
+div.highlight .-Color[class*=-BGC129] {
+ background-color: #AF00FF
+}
+
+div.highlight .-Color[class*=-C130] {
+ color: #AF5F00
+}
+
+div.highlight .-Color[class*=-BGC130] {
+ background-color: #AF5F00
+}
+
+div.highlight .-Color[class*=-C131] {
+ color: #AF5F5F
+}
+
+div.highlight .-Color[class*=-BGC131] {
+ background-color: #AF5F5F
+}
+
+div.highlight .-Color[class*=-C132] {
+ color: #AF5F87
+}
+
+div.highlight .-Color[class*=-BGC132] {
+ background-color: #AF5F87
+}
+
+div.highlight .-Color[class*=-C133] {
+ color: #AF5FAF
+}
+
+div.highlight .-Color[class*=-BGC133] {
+ background-color: #AF5FAF
+}
+
+div.highlight .-Color[class*=-C134] {
+ color: #AF5FD7
+}
+
+div.highlight .-Color[class*=-BGC134] {
+ background-color: #AF5FD7
+}
+
+div.highlight .-Color[class*=-C135] {
+ color: #AF5FFF
+}
+
+div.highlight .-Color[class*=-BGC135] {
+ background-color: #AF5FFF
+}
+
+div.highlight .-Color[class*=-C136] {
+ color: #AF8700
+}
+
+div.highlight .-Color[class*=-BGC136] {
+ background-color: #AF8700
+}
+
+div.highlight .-Color[class*=-C137] {
+ color: #AF875F
+}
+
+div.highlight .-Color[class*=-BGC137] {
+ background-color: #AF875F
+}
+
+div.highlight .-Color[class*=-C138] {
+ color: #AF8787
+}
+
+div.highlight .-Color[class*=-BGC138] {
+ background-color: #AF8787
+}
+
+div.highlight .-Color[class*=-C139] {
+ color: #AF87AF
+}
+
+div.highlight .-Color[class*=-BGC139] {
+ background-color: #AF87AF
+}
+
+div.highlight .-Color[class*=-C140] {
+ color: #AF87D7
+}
+
+div.highlight .-Color[class*=-BGC140] {
+ background-color: #AF87D7
+}
+
+div.highlight .-Color[class*=-C141] {
+ color: #AF87FF
+}
+
+div.highlight .-Color[class*=-BGC141] {
+ background-color: #AF87FF
+}
+
+div.highlight .-Color[class*=-C142] {
+ color: #AFAF00
+}
+
+div.highlight .-Color[class*=-BGC142] {
+ background-color: #AFAF00
+}
+
+div.highlight .-Color[class*=-C143] {
+ color: #AFAF5F
+}
+
+div.highlight .-Color[class*=-BGC143] {
+ background-color: #AFAF5F
+}
+
+div.highlight .-Color[class*=-C144] {
+ color: #AFAF87
+}
+
+div.highlight .-Color[class*=-BGC144] {
+ background-color: #AFAF87
+}
+
+div.highlight .-Color[class*=-C145] {
+ color: #AFAFAF
+}
+
+div.highlight .-Color[class*=-BGC145] {
+ background-color: #AFAFAF
+}
+
+div.highlight .-Color[class*=-C146] {
+ color: #AFAFD7
+}
+
+div.highlight .-Color[class*=-BGC146] {
+ background-color: #AFAFD7
+}
+
+div.highlight .-Color[class*=-C147] {
+ color: #AFAFFF
+}
+
+div.highlight .-Color[class*=-BGC147] {
+ background-color: #AFAFFF
+}
+
+div.highlight .-Color[class*=-C148] {
+ color: #AFD700
+}
+
+div.highlight .-Color[class*=-BGC148] {
+ background-color: #AFD700
+}
+
+div.highlight .-Color[class*=-C149] {
+ color: #AFD75F
+}
+
+div.highlight .-Color[class*=-BGC149] {
+ background-color: #AFD75F
+}
+
+div.highlight .-Color[class*=-C150] {
+ color: #AFD787
+}
+
+div.highlight .-Color[class*=-BGC150] {
+ background-color: #AFD787
+}
+
+div.highlight .-Color[class*=-C151] {
+ color: #AFD7AF
+}
+
+div.highlight .-Color[class*=-BGC151] {
+ background-color: #AFD7AF
+}
+
+div.highlight .-Color[class*=-C152] {
+ color: #AFD7D7
+}
+
+div.highlight .-Color[class*=-BGC152] {
+ background-color: #AFD7D7
+}
+
+div.highlight .-Color[class*=-C153] {
+ color: #AFD7FF
+}
+
+div.highlight .-Color[class*=-BGC153] {
+ background-color: #AFD7FF
+}
+
+div.highlight .-Color[class*=-C154] {
+ color: #AFFF00
+}
+
+div.highlight .-Color[class*=-BGC154] {
+ background-color: #AFFF00
+}
+
+div.highlight .-Color[class*=-C155] {
+ color: #AFFF5F
+}
+
+div.highlight .-Color[class*=-BGC155] {
+ background-color: #AFFF5F
+}
+
+div.highlight .-Color[class*=-C156] {
+ color: #AFFF87
+}
+
+div.highlight .-Color[class*=-BGC156] {
+ background-color: #AFFF87
+}
+
+div.highlight .-Color[class*=-C157] {
+ color: #AFFFAF
+}
+
+div.highlight .-Color[class*=-BGC157] {
+ background-color: #AFFFAF
+}
+
+div.highlight .-Color[class*=-C158] {
+ color: #AFFFD7
+}
+
+div.highlight .-Color[class*=-BGC158] {
+ background-color: #AFFFD7
+}
+
+div.highlight .-Color[class*=-C159] {
+ color: #AFFFFF
+}
+
+div.highlight .-Color[class*=-BGC159] {
+ background-color: #AFFFFF
+}
+
+div.highlight .-Color[class*=-C160] {
+ color: #D70000
+}
+
+div.highlight .-Color[class*=-BGC160] {
+ background-color: #D70000
+}
+
+div.highlight .-Color[class*=-C161] {
+ color: #D7005F
+}
+
+div.highlight .-Color[class*=-BGC161] {
+ background-color: #D7005F
+}
+
+div.highlight .-Color[class*=-C162] {
+ color: #D70087
+}
+
+div.highlight .-Color[class*=-BGC162] {
+ background-color: #D70087
+}
+
+div.highlight .-Color[class*=-C163] {
+ color: #D700AF
+}
+
+div.highlight .-Color[class*=-BGC163] {
+ background-color: #D700AF
+}
+
+div.highlight .-Color[class*=-C164] {
+ color: #D700D7
+}
+
+div.highlight .-Color[class*=-BGC164] {
+ background-color: #D700D7
+}
+
+div.highlight .-Color[class*=-C165] {
+ color: #D700FF
+}
+
+div.highlight .-Color[class*=-BGC165] {
+ background-color: #D700FF
+}
+
+div.highlight .-Color[class*=-C166] {
+ color: #D75F00
+}
+
+div.highlight .-Color[class*=-BGC166] {
+ background-color: #D75F00
+}
+
+div.highlight .-Color[class*=-C167] {
+ color: #D75F5F
+}
+
+div.highlight .-Color[class*=-BGC167] {
+ background-color: #D75F5F
+}
+
+div.highlight .-Color[class*=-C168] {
+ color: #D75F87
+}
+
+div.highlight .-Color[class*=-BGC168] {
+ background-color: #D75F87
+}
+
+div.highlight .-Color[class*=-C169] {
+ color: #D75FAF
+}
+
+div.highlight .-Color[class*=-BGC169] {
+ background-color: #D75FAF
+}
+
+div.highlight .-Color[class*=-C170] {
+ color: #D75FD7
+}
+
+div.highlight .-Color[class*=-BGC170] {
+ background-color: #D75FD7
+}
+
+div.highlight .-Color[class*=-C171] {
+ color: #D75FFF
+}
+
+div.highlight .-Color[class*=-BGC171] {
+ background-color: #D75FFF
+}
+
+div.highlight .-Color[class*=-C172] {
+ color: #D78700
+}
+
+div.highlight .-Color[class*=-BGC172] {
+ background-color: #D78700
+}
+
+div.highlight .-Color[class*=-C173] {
+ color: #D7875F
+}
+
+div.highlight .-Color[class*=-BGC173] {
+ background-color: #D7875F
+}
+
+div.highlight .-Color[class*=-C174] {
+ color: #D78787
+}
+
+div.highlight .-Color[class*=-BGC174] {
+ background-color: #D78787
+}
+
+div.highlight .-Color[class*=-C175] {
+ color: #D787AF
+}
+
+div.highlight .-Color[class*=-BGC175] {
+ background-color: #D787AF
+}
+
+div.highlight .-Color[class*=-C176] {
+ color: #D787D7
+}
+
+div.highlight .-Color[class*=-BGC176] {
+ background-color: #D787D7
+}
+
+div.highlight .-Color[class*=-C177] {
+ color: #D787FF
+}
+
+div.highlight .-Color[class*=-BGC177] {
+ background-color: #D787FF
+}
+
+div.highlight .-Color[class*=-C178] {
+ color: #D7AF00
+}
+
+div.highlight .-Color[class*=-BGC178] {
+ background-color: #D7AF00
+}
+
+div.highlight .-Color[class*=-C179] {
+ color: #D7AF5F
+}
+
+div.highlight .-Color[class*=-BGC179] {
+ background-color: #D7AF5F
+}
+
+div.highlight .-Color[class*=-C180] {
+ color: #D7AF87
+}
+
+div.highlight .-Color[class*=-BGC180] {
+ background-color: #D7AF87
+}
+
+div.highlight .-Color[class*=-C181] {
+ color: #D7AFAF
+}
+
+div.highlight .-Color[class*=-BGC181] {
+ background-color: #D7AFAF
+}
+
+div.highlight .-Color[class*=-C182] {
+ color: #D7AFD7
+}
+
+div.highlight .-Color[class*=-BGC182] {
+ background-color: #D7AFD7
+}
+
+div.highlight .-Color[class*=-C183] {
+ color: #D7AFFF
+}
+
+div.highlight .-Color[class*=-BGC183] {
+ background-color: #D7AFFF
+}
+
+div.highlight .-Color[class*=-C184] {
+ color: #D7D700
+}
+
+div.highlight .-Color[class*=-BGC184] {
+ background-color: #D7D700
+}
+
+div.highlight .-Color[class*=-C185] {
+ color: #D7D75F
+}
+
+div.highlight .-Color[class*=-BGC185] {
+ background-color: #D7D75F
+}
+
+div.highlight .-Color[class*=-C186] {
+ color: #D7D787
+}
+
+div.highlight .-Color[class*=-BGC186] {
+ background-color: #D7D787
+}
+
+div.highlight .-Color[class*=-C187] {
+ color: #D7D7AF
+}
+
+div.highlight .-Color[class*=-BGC187] {
+ background-color: #D7D7AF
+}
+
+div.highlight .-Color[class*=-C188] {
+ color: #D7D7D7
+}
+
+div.highlight .-Color[class*=-BGC188] {
+ background-color: #D7D7D7
+}
+
+div.highlight .-Color[class*=-C189] {
+ color: #D7D7FF
+}
+
+div.highlight .-Color[class*=-BGC189] {
+ background-color: #D7D7FF
+}
+
+div.highlight .-Color[class*=-C190] {
+ color: #D7FF00
+}
+
+div.highlight .-Color[class*=-BGC190] {
+ background-color: #D7FF00
+}
+
+div.highlight .-Color[class*=-C191] {
+ color: #D7FF5F
+}
+
+div.highlight .-Color[class*=-BGC191] {
+ background-color: #D7FF5F
+}
+
+div.highlight .-Color[class*=-C192] {
+ color: #D7FF87
+}
+
+div.highlight .-Color[class*=-BGC192] {
+ background-color: #D7FF87
+}
+
+div.highlight .-Color[class*=-C193] {
+ color: #D7FFAF
+}
+
+div.highlight .-Color[class*=-BGC193] {
+ background-color: #D7FFAF
+}
+
+div.highlight .-Color[class*=-C194] {
+ color: #D7FFD7
+}
+
+div.highlight .-Color[class*=-BGC194] {
+ background-color: #D7FFD7
+}
+
+div.highlight .-Color[class*=-C195] {
+ color: #D7FFFF
+}
+
+div.highlight .-Color[class*=-BGC195] {
+ background-color: #D7FFFF
+}
+
+div.highlight .-Color[class*=-C196] {
+ color: #FF0000
+}
+
+div.highlight .-Color[class*=-BGC196] {
+ background-color: #FF0000
+}
+
+div.highlight .-Color[class*=-C197] {
+ color: #FF005F
+}
+
+div.highlight .-Color[class*=-BGC197] {
+ background-color: #FF005F
+}
+
+div.highlight .-Color[class*=-C198] {
+ color: #FF0087
+}
+
+div.highlight .-Color[class*=-BGC198] {
+ background-color: #FF0087
+}
+
+div.highlight .-Color[class*=-C199] {
+ color: #FF00AF
+}
+
+div.highlight .-Color[class*=-BGC199] {
+ background-color: #FF00AF
+}
+
+div.highlight .-Color[class*=-C200] {
+ color: #FF00D7
+}
+
+div.highlight .-Color[class*=-BGC200] {
+ background-color: #FF00D7
+}
+
+div.highlight .-Color[class*=-C201] {
+ color: #FF00FF
+}
+
+div.highlight .-Color[class*=-BGC201] {
+ background-color: #FF00FF
+}
+
+div.highlight .-Color[class*=-C202] {
+ color: #FF5F00
+}
+
+div.highlight .-Color[class*=-BGC202] {
+ background-color: #FF5F00
+}
+
+div.highlight .-Color[class*=-C203] {
+ color: #FF5F5F
+}
+
+div.highlight .-Color[class*=-BGC203] {
+ background-color: #FF5F5F
+}
+
+div.highlight .-Color[class*=-C204] {
+ color: #FF5F87
+}
+
+div.highlight .-Color[class*=-BGC204] {
+ background-color: #FF5F87
+}
+
+div.highlight .-Color[class*=-C205] {
+ color: #FF5FAF
+}
+
+div.highlight .-Color[class*=-BGC205] {
+ background-color: #FF5FAF
+}
+
+div.highlight .-Color[class*=-C206] {
+ color: #FF5FD7
+}
+
+div.highlight .-Color[class*=-BGC206] {
+ background-color: #FF5FD7
+}
+
+div.highlight .-Color[class*=-C207] {
+ color: #FF5FFF
+}
+
+div.highlight .-Color[class*=-BGC207] {
+ background-color: #FF5FFF
+}
+
+div.highlight .-Color[class*=-C208] {
+ color: #FF8700
+}
+
+div.highlight .-Color[class*=-BGC208] {
+ background-color: #FF8700
+}
+
+div.highlight .-Color[class*=-C209] {
+ color: #FF875F
+}
+
+div.highlight .-Color[class*=-BGC209] {
+ background-color: #FF875F
+}
+
+div.highlight .-Color[class*=-C210] {
+ color: #FF8787
+}
+
+div.highlight .-Color[class*=-BGC210] {
+ background-color: #FF8787
+}
+
+div.highlight .-Color[class*=-C211] {
+ color: #FF87AF
+}
+
+div.highlight .-Color[class*=-BGC211] {
+ background-color: #FF87AF
+}
+
+div.highlight .-Color[class*=-C212] {
+ color: #FF87D7
+}
+
+div.highlight .-Color[class*=-BGC212] {
+ background-color: #FF87D7
+}
+
+div.highlight .-Color[class*=-C213] {
+ color: #FF87FF
+}
+
+div.highlight .-Color[class*=-BGC213] {
+ background-color: #FF87FF
+}
+
+div.highlight .-Color[class*=-C214] {
+ color: #FFAF00
+}
+
+div.highlight .-Color[class*=-BGC214] {
+ background-color: #FFAF00
+}
+
+div.highlight .-Color[class*=-C215] {
+ color: #FFAF5F
+}
+
+div.highlight .-Color[class*=-BGC215] {
+ background-color: #FFAF5F
+}
+
+div.highlight .-Color[class*=-C216] {
+ color: #FFAF87
+}
+
+div.highlight .-Color[class*=-BGC216] {
+ background-color: #FFAF87
+}
+
+div.highlight .-Color[class*=-C217] {
+ color: #FFAFAF
+}
+
+div.highlight .-Color[class*=-BGC217] {
+ background-color: #FFAFAF
+}
+
+div.highlight .-Color[class*=-C218] {
+ color: #FFAFD7
+}
+
+div.highlight .-Color[class*=-BGC218] {
+ background-color: #FFAFD7
+}
+
+div.highlight .-Color[class*=-C219] {
+ color: #FFAFFF
+}
+
+div.highlight .-Color[class*=-BGC219] {
+ background-color: #FFAFFF
+}
+
+div.highlight .-Color[class*=-C220] {
+ color: #FFD700
+}
+
+div.highlight .-Color[class*=-BGC220] {
+ background-color: #FFD700
+}
+
+div.highlight .-Color[class*=-C221] {
+ color: #FFD75F
+}
+
+div.highlight .-Color[class*=-BGC221] {
+ background-color: #FFD75F
+}
+
+div.highlight .-Color[class*=-C222] {
+ color: #FFD787
+}
+
+div.highlight .-Color[class*=-BGC222] {
+ background-color: #FFD787
+}
+
+div.highlight .-Color[class*=-C223] {
+ color: #FFD7AF
+}
+
+div.highlight .-Color[class*=-BGC223] {
+ background-color: #FFD7AF
+}
+
+div.highlight .-Color[class*=-C224] {
+ color: #FFD7D7
+}
+
+div.highlight .-Color[class*=-BGC224] {
+ background-color: #FFD7D7
+}
+
+div.highlight .-Color[class*=-C225] {
+ color: #FFD7FF
+}
+
+div.highlight .-Color[class*=-BGC225] {
+ background-color: #FFD7FF
+}
+
+div.highlight .-Color[class*=-C226] {
+ color: #FFFF00
+}
+
+div.highlight .-Color[class*=-BGC226] {
+ background-color: #FFFF00
+}
+
+div.highlight .-Color[class*=-C227] {
+ color: #FFFF5F
+}
+
+div.highlight .-Color[class*=-BGC227] {
+ background-color: #FFFF5F
+}
+
+div.highlight .-Color[class*=-C228] {
+ color: #FFFF87
+}
+
+div.highlight .-Color[class*=-BGC228] {
+ background-color: #FFFF87
+}
+
+div.highlight .-Color[class*=-C229] {
+ color: #FFFFAF
+}
+
+div.highlight .-Color[class*=-BGC229] {
+ background-color: #FFFFAF
+}
+
+div.highlight .-Color[class*=-C230] {
+ color: #FFFFD7
+}
+
+div.highlight .-Color[class*=-BGC230] {
+ background-color: #FFFFD7
+}
+
+div.highlight .-Color[class*=-C231] {
+ color: #FFFFFF
+}
+
+div.highlight .-Color[class*=-BGC231] {
+ background-color: #FFFFFF
+}
+
+div.highlight .-Color[class*=-C232] {
+ color: #080808
+}
+
+div.highlight .-Color[class*=-BGC232] {
+ background-color: #080808
+}
+
+div.highlight .-Color[class*=-C233] {
+ color: #121212
+}
+
+div.highlight .-Color[class*=-BGC233] {
+ background-color: #121212
+}
+
+div.highlight .-Color[class*=-C234] {
+ color: #1C1C1C
+}
+
+div.highlight .-Color[class*=-BGC234] {
+ background-color: #1C1C1C
+}
+
+div.highlight .-Color[class*=-C235] {
+ color: #262626
+}
+
+div.highlight .-Color[class*=-BGC235] {
+ background-color: #262626
+}
+
+div.highlight .-Color[class*=-C236] {
+ color: #303030
+}
+
+div.highlight .-Color[class*=-BGC236] {
+ background-color: #303030
+}
+
+div.highlight .-Color[class*=-C237] {
+ color: #3A3A3A
+}
+
+div.highlight .-Color[class*=-BGC237] {
+ background-color: #3A3A3A
+}
+
+div.highlight .-Color[class*=-C238] {
+ color: #444444
+}
+
+div.highlight .-Color[class*=-BGC238] {
+ background-color: #444444
+}
+
+div.highlight .-Color[class*=-C239] {
+ color: #4E4E4E
+}
+
+div.highlight .-Color[class*=-BGC239] {
+ background-color: #4E4E4E
+}
+
+div.highlight .-Color[class*=-C240] {
+ color: #585858
+}
+
+div.highlight .-Color[class*=-BGC240] {
+ background-color: #585858
+}
+
+div.highlight .-Color[class*=-C241] {
+ color: #626262
+}
+
+div.highlight .-Color[class*=-BGC241] {
+ background-color: #626262
+}
+
+div.highlight .-Color[class*=-C242] {
+ color: #6C6C6C
+}
+
+div.highlight .-Color[class*=-BGC242] {
+ background-color: #6C6C6C
+}
+
+div.highlight .-Color[class*=-C243] {
+ color: #767676
+}
+
+div.highlight .-Color[class*=-BGC243] {
+ background-color: #767676
+}
+
+div.highlight .-Color[class*=-C244] {
+ color: #808080
+}
+
+div.highlight .-Color[class*=-BGC244] {
+ background-color: #808080
+}
+
+div.highlight .-Color[class*=-C245] {
+ color: #8A8A8A
+}
+
+div.highlight .-Color[class*=-BGC245] {
+ background-color: #8A8A8A
+}
+
+div.highlight .-Color[class*=-C246] {
+ color: #949494
+}
+
+div.highlight .-Color[class*=-BGC246] {
+ background-color: #949494
+}
+
+div.highlight .-Color[class*=-C247] {
+ color: #9E9E9E
+}
+
+div.highlight .-Color[class*=-BGC247] {
+ background-color: #9E9E9E
+}
+
+div.highlight .-Color[class*=-C248] {
+ color: #A8A8A8
+}
+
+div.highlight .-Color[class*=-BGC248] {
+ background-color: #A8A8A8
+}
+
+div.highlight .-Color[class*=-C249] {
+ color: #B2B2B2
+}
+
+div.highlight .-Color[class*=-BGC249] {
+ background-color: #B2B2B2
+}
+
+div.highlight .-Color[class*=-C250] {
+ color: #BCBCBC
+}
+
+div.highlight .-Color[class*=-BGC250] {
+ background-color: #BCBCBC
+}
+
+div.highlight .-Color[class*=-C251] {
+ color: #C6C6C6
+}
+
+div.highlight .-Color[class*=-BGC251] {
+ background-color: #C6C6C6
+}
+
+div.highlight .-Color[class*=-C252] {
+ color: #D0D0D0
+}
+
+div.highlight .-Color[class*=-BGC252] {
+ background-color: #D0D0D0
+}
+
+div.highlight .-Color[class*=-C253] {
+ color: #DADADA
+}
+
+div.highlight .-Color[class*=-BGC253] {
+ background-color: #DADADA
+}
+
+div.highlight .-Color[class*=-C254] {
+ color: #E4E4E4
+}
+
+div.highlight .-Color[class*=-BGC254] {
+ background-color: #E4E4E4
+}
+
+div.highlight .-Color[class*=-C255] {
+ color: #EEEEEE
+}
+
+div.highlight .-Color[class*=-BGC255] {
+ background-color: #EEEEEE
+}
diff --git a/singlehtml/_static/plus.png b/singlehtml/_static/plus.png
new file mode 100644
index 0000000..7107cec
Binary files /dev/null and b/singlehtml/_static/plus.png differ
diff --git a/singlehtml/_static/pygments.css b/singlehtml/_static/pygments.css
new file mode 100644
index 0000000..6f8b210
--- /dev/null
+++ b/singlehtml/_static/pygments.css
@@ -0,0 +1,75 @@
+pre { line-height: 125%; }
+td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
+span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
+td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
+span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
+.highlight .hll { background-color: #ffffcc }
+.highlight { background: #f8f8f8; }
+.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */
+.highlight .err { border: 1px solid #F00 } /* Error */
+.highlight .k { color: #008000; font-weight: bold } /* Keyword */
+.highlight .o { color: #666 } /* Operator */
+.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */
+.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */
+.highlight .cp { color: #9C6500 } /* Comment.Preproc */
+.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */
+.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */
+.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */
+.highlight .gd { color: #A00000 } /* Generic.Deleted */
+.highlight .ge { font-style: italic } /* Generic.Emph */
+.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
+.highlight .gr { color: #E40000 } /* Generic.Error */
+.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.highlight .gi { color: #008400 } /* Generic.Inserted */
+.highlight .go { color: #717171 } /* Generic.Output */
+.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
+.highlight .gs { font-weight: bold } /* Generic.Strong */
+.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.highlight .gt { color: #04D } /* Generic.Traceback */
+.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
+.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
+.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
+.highlight .kp { color: #008000 } /* Keyword.Pseudo */
+.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
+.highlight .kt { color: #B00040 } /* Keyword.Type */
+.highlight .m { color: #666 } /* Literal.Number */
+.highlight .s { color: #BA2121 } /* Literal.String */
+.highlight .na { color: #687822 } /* Name.Attribute */
+.highlight .nb { color: #008000 } /* Name.Builtin */
+.highlight .nc { color: #00F; font-weight: bold } /* Name.Class */
+.highlight .no { color: #800 } /* Name.Constant */
+.highlight .nd { color: #A2F } /* Name.Decorator */
+.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */
+.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */
+.highlight .nf { color: #00F } /* Name.Function */
+.highlight .nl { color: #767600 } /* Name.Label */
+.highlight .nn { color: #00F; font-weight: bold } /* Name.Namespace */
+.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */
+.highlight .nv { color: #19177C } /* Name.Variable */
+.highlight .ow { color: #A2F; font-weight: bold } /* Operator.Word */
+.highlight .w { color: #BBB } /* Text.Whitespace */
+.highlight .mb { color: #666 } /* Literal.Number.Bin */
+.highlight .mf { color: #666 } /* Literal.Number.Float */
+.highlight .mh { color: #666 } /* Literal.Number.Hex */
+.highlight .mi { color: #666 } /* Literal.Number.Integer */
+.highlight .mo { color: #666 } /* Literal.Number.Oct */
+.highlight .sa { color: #BA2121 } /* Literal.String.Affix */
+.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */
+.highlight .sc { color: #BA2121 } /* Literal.String.Char */
+.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */
+.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
+.highlight .s2 { color: #BA2121 } /* Literal.String.Double */
+.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */
+.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */
+.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */
+.highlight .sx { color: #008000 } /* Literal.String.Other */
+.highlight .sr { color: #A45A77 } /* Literal.String.Regex */
+.highlight .s1 { color: #BA2121 } /* Literal.String.Single */
+.highlight .ss { color: #19177C } /* Literal.String.Symbol */
+.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */
+.highlight .fm { color: #00F } /* Name.Function.Magic */
+.highlight .vc { color: #19177C } /* Name.Variable.Class */
+.highlight .vg { color: #19177C } /* Name.Variable.Global */
+.highlight .vi { color: #19177C } /* Name.Variable.Instance */
+.highlight .vm { color: #19177C } /* Name.Variable.Magic */
+.highlight .il { color: #666 } /* Literal.Number.Integer.Long */
\ No newline at end of file
diff --git a/singlehtml/_static/searchtools.js b/singlehtml/_static/searchtools.js
new file mode 100644
index 0000000..b08d58c
--- /dev/null
+++ b/singlehtml/_static/searchtools.js
@@ -0,0 +1,620 @@
+/*
+ * searchtools.js
+ * ~~~~~~~~~~~~~~~~
+ *
+ * Sphinx JavaScript utilities for the full-text search.
+ *
+ * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
+ * :license: BSD, see LICENSE for details.
+ *
+ */
+"use strict";
+
+/**
+ * Simple result scoring code.
+ */
+if (typeof Scorer === "undefined") {
+ var Scorer = {
+ // Implement the following function to further tweak the score for each result
+ // The function takes a result array [docname, title, anchor, descr, score, filename]
+ // and returns the new score.
+ /*
+ score: result => {
+ const [docname, title, anchor, descr, score, filename] = result
+ return score
+ },
+ */
+
+ // query matches the full name of an object
+ objNameMatch: 11,
+ // or matches in the last dotted part of the object name
+ objPartialMatch: 6,
+ // Additive scores depending on the priority of the object
+ objPrio: {
+ 0: 15, // used to be importantResults
+ 1: 5, // used to be objectResults
+ 2: -5, // used to be unimportantResults
+ },
+ // Used when the priority is not in the mapping.
+ objPrioDefault: 0,
+
+ // query found in title
+ title: 15,
+ partialTitle: 7,
+ // query found in terms
+ term: 5,
+ partialTerm: 2,
+ };
+}
+
+const _removeChildren = (element) => {
+ while (element && element.lastChild) element.removeChild(element.lastChild);
+};
+
+/**
+ * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
+ */
+const _escapeRegExp = (string) =>
+ string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
+
+const _displayItem = (item, searchTerms, highlightTerms) => {
+ const docBuilder = DOCUMENTATION_OPTIONS.BUILDER;
+ const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX;
+ const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX;
+ const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY;
+ const contentRoot = document.documentElement.dataset.content_root;
+
+ const [docName, title, anchor, descr, score, _filename] = item;
+
+ let listItem = document.createElement("li");
+ let requestUrl;
+ let linkUrl;
+ if (docBuilder === "dirhtml") {
+ // dirhtml builder
+ let dirname = docName + "/";
+ if (dirname.match(/\/index\/$/))
+ dirname = dirname.substring(0, dirname.length - 6);
+ else if (dirname === "index/") dirname = "";
+ requestUrl = contentRoot + dirname;
+ linkUrl = requestUrl;
+ } else {
+ // normal html builders
+ requestUrl = contentRoot + docName + docFileSuffix;
+ linkUrl = docName + docLinkSuffix;
+ }
+ let linkEl = listItem.appendChild(document.createElement("a"));
+ linkEl.href = linkUrl + anchor;
+ linkEl.dataset.score = score;
+ linkEl.innerHTML = title;
+ if (descr) {
+ listItem.appendChild(document.createElement("span")).innerHTML =
+ " (" + descr + ")";
+ // highlight search terms in the description
+ if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
+ highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
+ }
+ else if (showSearchSummary)
+ fetch(requestUrl)
+ .then((responseData) => responseData.text())
+ .then((data) => {
+ if (data)
+ listItem.appendChild(
+ Search.makeSearchSummary(data, searchTerms, anchor)
+ );
+ // highlight search terms in the summary
+ if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
+ highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
+ });
+ Search.output.appendChild(listItem);
+};
+const _finishSearch = (resultCount) => {
+ Search.stopPulse();
+ Search.title.innerText = _("Search Results");
+ if (!resultCount)
+ Search.status.innerText = Documentation.gettext(
+ "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories."
+ );
+ else
+ Search.status.innerText = _(
+ "Search finished, found ${resultCount} page(s) matching the search query."
+ ).replace('${resultCount}', resultCount);
+};
+const _displayNextItem = (
+ results,
+ resultCount,
+ searchTerms,
+ highlightTerms,
+) => {
+ // results left, load the summary and display it
+ // this is intended to be dynamic (don't sub resultsCount)
+ if (results.length) {
+ _displayItem(results.pop(), searchTerms, highlightTerms);
+ setTimeout(
+ () => _displayNextItem(results, resultCount, searchTerms, highlightTerms),
+ 5
+ );
+ }
+ // search finished, update title and status message
+ else _finishSearch(resultCount);
+};
+// Helper function used by query() to order search results.
+// Each input is an array of [docname, title, anchor, descr, score, filename].
+// Order the results by score (in opposite order of appearance, since the
+// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically.
+const _orderResultsByScoreThenName = (a, b) => {
+ const leftScore = a[4];
+ const rightScore = b[4];
+ if (leftScore === rightScore) {
+ // same score: sort alphabetically
+ const leftTitle = a[1].toLowerCase();
+ const rightTitle = b[1].toLowerCase();
+ if (leftTitle === rightTitle) return 0;
+ return leftTitle > rightTitle ? -1 : 1; // inverted is intentional
+ }
+ return leftScore > rightScore ? 1 : -1;
+};
+
+/**
+ * Default splitQuery function. Can be overridden in ``sphinx.search`` with a
+ * custom function per language.
+ *
+ * The regular expression works by splitting the string on consecutive characters
+ * that are not Unicode letters, numbers, underscores, or emoji characters.
+ * This is the same as ``\W+`` in Python, preserving the surrogate pair area.
+ */
+if (typeof splitQuery === "undefined") {
+ var splitQuery = (query) => query
+ .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu)
+ .filter(term => term) // remove remaining empty strings
+}
+
+/**
+ * Search Module
+ */
+const Search = {
+ _index: null,
+ _queued_query: null,
+ _pulse_status: -1,
+
+ htmlToText: (htmlString, anchor) => {
+ const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html');
+ for (const removalQuery of [".headerlink", "script", "style"]) {
+ htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() });
+ }
+ if (anchor) {
+ const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`);
+ if (anchorContent) return anchorContent.textContent;
+
+ console.warn(
+ `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.`
+ );
+ }
+
+ // if anchor not specified or not found, fall back to main content
+ const docContent = htmlElement.querySelector('[role="main"]');
+ if (docContent) return docContent.textContent;
+
+ console.warn(
+ "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template."
+ );
+ return "";
+ },
+
+ init: () => {
+ const query = new URLSearchParams(window.location.search).get("q");
+ document
+ .querySelectorAll('input[name="q"]')
+ .forEach((el) => (el.value = query));
+ if (query) Search.performSearch(query);
+ },
+
+ loadIndex: (url) =>
+ (document.body.appendChild(document.createElement("script")).src = url),
+
+ setIndex: (index) => {
+ Search._index = index;
+ if (Search._queued_query !== null) {
+ const query = Search._queued_query;
+ Search._queued_query = null;
+ Search.query(query);
+ }
+ },
+
+ hasIndex: () => Search._index !== null,
+
+ deferQuery: (query) => (Search._queued_query = query),
+
+ stopPulse: () => (Search._pulse_status = -1),
+
+ startPulse: () => {
+ if (Search._pulse_status >= 0) return;
+
+ const pulse = () => {
+ Search._pulse_status = (Search._pulse_status + 1) % 4;
+ Search.dots.innerText = ".".repeat(Search._pulse_status);
+ if (Search._pulse_status >= 0) window.setTimeout(pulse, 500);
+ };
+ pulse();
+ },
+
+ /**
+ * perform a search for something (or wait until index is loaded)
+ */
+ performSearch: (query) => {
+ // create the required interface elements
+ const searchText = document.createElement("h2");
+ searchText.textContent = _("Searching");
+ const searchSummary = document.createElement("p");
+ searchSummary.classList.add("search-summary");
+ searchSummary.innerText = "";
+ const searchList = document.createElement("ul");
+ searchList.classList.add("search");
+
+ const out = document.getElementById("search-results");
+ Search.title = out.appendChild(searchText);
+ Search.dots = Search.title.appendChild(document.createElement("span"));
+ Search.status = out.appendChild(searchSummary);
+ Search.output = out.appendChild(searchList);
+
+ const searchProgress = document.getElementById("search-progress");
+ // Some themes don't use the search progress node
+ if (searchProgress) {
+ searchProgress.innerText = _("Preparing search...");
+ }
+ Search.startPulse();
+
+ // index already loaded, the browser was quick!
+ if (Search.hasIndex()) Search.query(query);
+ else Search.deferQuery(query);
+ },
+
+ _parseQuery: (query) => {
+ // stem the search terms and add them to the correct list
+ const stemmer = new Stemmer();
+ const searchTerms = new Set();
+ const excludedTerms = new Set();
+ const highlightTerms = new Set();
+ const objectTerms = new Set(splitQuery(query.toLowerCase().trim()));
+ splitQuery(query.trim()).forEach((queryTerm) => {
+ const queryTermLower = queryTerm.toLowerCase();
+
+ // maybe skip this "word"
+ // stopwords array is from language_data.js
+ if (
+ stopwords.indexOf(queryTermLower) !== -1 ||
+ queryTerm.match(/^\d+$/)
+ )
+ return;
+
+ // stem the word
+ let word = stemmer.stemWord(queryTermLower);
+ // select the correct list
+ if (word[0] === "-") excludedTerms.add(word.substr(1));
+ else {
+ searchTerms.add(word);
+ highlightTerms.add(queryTermLower);
+ }
+ });
+
+ if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js
+ localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" "))
+ }
+
+ // console.debug("SEARCH: searching for:");
+ // console.info("required: ", [...searchTerms]);
+ // console.info("excluded: ", [...excludedTerms]);
+
+ return [query, searchTerms, excludedTerms, highlightTerms, objectTerms];
+ },
+
+ /**
+ * execute search (requires search index to be loaded)
+ */
+ _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => {
+ const filenames = Search._index.filenames;
+ const docNames = Search._index.docnames;
+ const titles = Search._index.titles;
+ const allTitles = Search._index.alltitles;
+ const indexEntries = Search._index.indexentries;
+
+ // Collect multiple result groups to be sorted separately and then ordered.
+ // Each is an array of [docname, title, anchor, descr, score, filename].
+ const normalResults = [];
+ const nonMainIndexResults = [];
+
+ _removeChildren(document.getElementById("search-progress"));
+
+ const queryLower = query.toLowerCase().trim();
+ for (const [title, foundTitles] of Object.entries(allTitles)) {
+ if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) {
+ for (const [file, id] of foundTitles) {
+ const score = Math.round(Scorer.title * queryLower.length / title.length);
+ const boost = titles[file] === title ? 1 : 0; // add a boost for document titles
+ normalResults.push([
+ docNames[file],
+ titles[file] !== title ? `${titles[file]} > ${title}` : title,
+ id !== null ? "#" + id : "",
+ null,
+ score + boost,
+ filenames[file],
+ ]);
+ }
+ }
+ }
+
+ // search for explicit entries in index directives
+ for (const [entry, foundEntries] of Object.entries(indexEntries)) {
+ if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) {
+ for (const [file, id, isMain] of foundEntries) {
+ const score = Math.round(100 * queryLower.length / entry.length);
+ const result = [
+ docNames[file],
+ titles[file],
+ id ? "#" + id : "",
+ null,
+ score,
+ filenames[file],
+ ];
+ if (isMain) {
+ normalResults.push(result);
+ } else {
+ nonMainIndexResults.push(result);
+ }
+ }
+ }
+ }
+
+ // lookup as object
+ objectTerms.forEach((term) =>
+ normalResults.push(...Search.performObjectSearch(term, objectTerms))
+ );
+
+ // lookup as search terms in fulltext
+ normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms));
+
+ // let the scorer override scores with a custom scoring function
+ if (Scorer.score) {
+ normalResults.forEach((item) => (item[4] = Scorer.score(item)));
+ nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item)));
+ }
+
+ // Sort each group of results by score and then alphabetically by name.
+ normalResults.sort(_orderResultsByScoreThenName);
+ nonMainIndexResults.sort(_orderResultsByScoreThenName);
+
+ // Combine the result groups in (reverse) order.
+ // Non-main index entries are typically arbitrary cross-references,
+ // so display them after other results.
+ let results = [...nonMainIndexResults, ...normalResults];
+
+ // remove duplicate search results
+ // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept
+ let seen = new Set();
+ results = results.reverse().reduce((acc, result) => {
+ let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(',');
+ if (!seen.has(resultStr)) {
+ acc.push(result);
+ seen.add(resultStr);
+ }
+ return acc;
+ }, []);
+
+ return results.reverse();
+ },
+
+ query: (query) => {
+ const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query);
+ const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms);
+
+ // for debugging
+ //Search.lastresults = results.slice(); // a copy
+ // console.info("search results:", Search.lastresults);
+
+ // print the results
+ _displayNextItem(results, results.length, searchTerms, highlightTerms);
+ },
+
+ /**
+ * search for object names
+ */
+ performObjectSearch: (object, objectTerms) => {
+ const filenames = Search._index.filenames;
+ const docNames = Search._index.docnames;
+ const objects = Search._index.objects;
+ const objNames = Search._index.objnames;
+ const titles = Search._index.titles;
+
+ const results = [];
+
+ const objectSearchCallback = (prefix, match) => {
+ const name = match[4]
+ const fullname = (prefix ? prefix + "." : "") + name;
+ const fullnameLower = fullname.toLowerCase();
+ if (fullnameLower.indexOf(object) < 0) return;
+
+ let score = 0;
+ const parts = fullnameLower.split(".");
+
+ // check for different match types: exact matches of full name or
+ // "last name" (i.e. last dotted part)
+ if (fullnameLower === object || parts.slice(-1)[0] === object)
+ score += Scorer.objNameMatch;
+ else if (parts.slice(-1)[0].indexOf(object) > -1)
+ score += Scorer.objPartialMatch; // matches in last name
+
+ const objName = objNames[match[1]][2];
+ const title = titles[match[0]];
+
+ // If more than one term searched for, we require other words to be
+ // found in the name/title/description
+ const otherTerms = new Set(objectTerms);
+ otherTerms.delete(object);
+ if (otherTerms.size > 0) {
+ const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase();
+ if (
+ [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0)
+ )
+ return;
+ }
+
+ let anchor = match[3];
+ if (anchor === "") anchor = fullname;
+ else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname;
+
+ const descr = objName + _(", in ") + title;
+
+ // add custom score for some objects according to scorer
+ if (Scorer.objPrio.hasOwnProperty(match[2]))
+ score += Scorer.objPrio[match[2]];
+ else score += Scorer.objPrioDefault;
+
+ results.push([
+ docNames[match[0]],
+ fullname,
+ "#" + anchor,
+ descr,
+ score,
+ filenames[match[0]],
+ ]);
+ };
+ Object.keys(objects).forEach((prefix) =>
+ objects[prefix].forEach((array) =>
+ objectSearchCallback(prefix, array)
+ )
+ );
+ return results;
+ },
+
+ /**
+ * search for full-text terms in the index
+ */
+ performTermsSearch: (searchTerms, excludedTerms) => {
+ // prepare search
+ const terms = Search._index.terms;
+ const titleTerms = Search._index.titleterms;
+ const filenames = Search._index.filenames;
+ const docNames = Search._index.docnames;
+ const titles = Search._index.titles;
+
+ const scoreMap = new Map();
+ const fileMap = new Map();
+
+ // perform the search on the required terms
+ searchTerms.forEach((word) => {
+ const files = [];
+ const arr = [
+ { files: terms[word], score: Scorer.term },
+ { files: titleTerms[word], score: Scorer.title },
+ ];
+ // add support for partial matches
+ if (word.length > 2) {
+ const escapedWord = _escapeRegExp(word);
+ if (!terms.hasOwnProperty(word)) {
+ Object.keys(terms).forEach((term) => {
+ if (term.match(escapedWord))
+ arr.push({ files: terms[term], score: Scorer.partialTerm });
+ });
+ }
+ if (!titleTerms.hasOwnProperty(word)) {
+ Object.keys(titleTerms).forEach((term) => {
+ if (term.match(escapedWord))
+ arr.push({ files: titleTerms[term], score: Scorer.partialTitle });
+ });
+ }
+ }
+
+ // no match but word was a required one
+ if (arr.every((record) => record.files === undefined)) return;
+
+ // found search word in contents
+ arr.forEach((record) => {
+ if (record.files === undefined) return;
+
+ let recordFiles = record.files;
+ if (recordFiles.length === undefined) recordFiles = [recordFiles];
+ files.push(...recordFiles);
+
+ // set score for the word in each file
+ recordFiles.forEach((file) => {
+ if (!scoreMap.has(file)) scoreMap.set(file, {});
+ scoreMap.get(file)[word] = record.score;
+ });
+ });
+
+ // create the mapping
+ files.forEach((file) => {
+ if (!fileMap.has(file)) fileMap.set(file, [word]);
+ else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word);
+ });
+ });
+
+ // now check if the files don't contain excluded terms
+ const results = [];
+ for (const [file, wordList] of fileMap) {
+ // check if all requirements are matched
+
+ // as search terms with length < 3 are discarded
+ const filteredTermCount = [...searchTerms].filter(
+ (term) => term.length > 2
+ ).length;
+ if (
+ wordList.length !== searchTerms.size &&
+ wordList.length !== filteredTermCount
+ )
+ continue;
+
+ // ensure that none of the excluded terms is in the search result
+ if (
+ [...excludedTerms].some(
+ (term) =>
+ terms[term] === file ||
+ titleTerms[term] === file ||
+ (terms[term] || []).includes(file) ||
+ (titleTerms[term] || []).includes(file)
+ )
+ )
+ break;
+
+ // select one (max) score for the file.
+ const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w]));
+ // add result to the result list
+ results.push([
+ docNames[file],
+ titles[file],
+ "",
+ null,
+ score,
+ filenames[file],
+ ]);
+ }
+ return results;
+ },
+
+ /**
+ * helper function to return a node containing the
+ * search summary for a given text. keywords is a list
+ * of stemmed words.
+ */
+ makeSearchSummary: (htmlText, keywords, anchor) => {
+ const text = Search.htmlToText(htmlText, anchor);
+ if (text === "") return null;
+
+ const textLower = text.toLowerCase();
+ const actualStartPosition = [...keywords]
+ .map((k) => textLower.indexOf(k.toLowerCase()))
+ .filter((i) => i > -1)
+ .slice(-1)[0];
+ const startWithContext = Math.max(actualStartPosition - 120, 0);
+
+ const top = startWithContext === 0 ? "" : "...";
+ const tail = startWithContext + 240 < text.length ? "..." : "";
+
+ let summary = document.createElement("p");
+ summary.classList.add("context");
+ summary.textContent = top + text.substr(startWithContext, 240).trim() + tail;
+
+ return summary;
+ },
+};
+
+_ready(Search.init);
diff --git a/singlehtml/_static/sphinx_highlight.js b/singlehtml/_static/sphinx_highlight.js
new file mode 100644
index 0000000..8a96c69
--- /dev/null
+++ b/singlehtml/_static/sphinx_highlight.js
@@ -0,0 +1,154 @@
+/* Highlighting utilities for Sphinx HTML documentation. */
+"use strict";
+
+const SPHINX_HIGHLIGHT_ENABLED = true
+
+/**
+ * highlight a given string on a node by wrapping it in
+ * span elements with the given class name.
+ */
+const _highlight = (node, addItems, text, className) => {
+ if (node.nodeType === Node.TEXT_NODE) {
+ const val = node.nodeValue;
+ const parent = node.parentNode;
+ const pos = val.toLowerCase().indexOf(text);
+ if (
+ pos >= 0 &&
+ !parent.classList.contains(className) &&
+ !parent.classList.contains("nohighlight")
+ ) {
+ let span;
+
+ const closestNode = parent.closest("body, svg, foreignObject");
+ const isInSVG = closestNode && closestNode.matches("svg");
+ if (isInSVG) {
+ span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
+ } else {
+ span = document.createElement("span");
+ span.classList.add(className);
+ }
+
+ span.appendChild(document.createTextNode(val.substr(pos, text.length)));
+ const rest = document.createTextNode(val.substr(pos + text.length));
+ parent.insertBefore(
+ span,
+ parent.insertBefore(
+ rest,
+ node.nextSibling
+ )
+ );
+ node.nodeValue = val.substr(0, pos);
+ /* There may be more occurrences of search term in this node. So call this
+ * function recursively on the remaining fragment.
+ */
+ _highlight(rest, addItems, text, className);
+
+ if (isInSVG) {
+ const rect = document.createElementNS(
+ "http://www.w3.org/2000/svg",
+ "rect"
+ );
+ const bbox = parent.getBBox();
+ rect.x.baseVal.value = bbox.x;
+ rect.y.baseVal.value = bbox.y;
+ rect.width.baseVal.value = bbox.width;
+ rect.height.baseVal.value = bbox.height;
+ rect.setAttribute("class", className);
+ addItems.push({ parent: parent, target: rect });
+ }
+ }
+ } else if (node.matches && !node.matches("button, select, textarea")) {
+ node.childNodes.forEach((el) => _highlight(el, addItems, text, className));
+ }
+};
+const _highlightText = (thisNode, text, className) => {
+ let addItems = [];
+ _highlight(thisNode, addItems, text, className);
+ addItems.forEach((obj) =>
+ obj.parent.insertAdjacentElement("beforebegin", obj.target)
+ );
+};
+
+/**
+ * Small JavaScript module for the documentation.
+ */
+const SphinxHighlight = {
+
+ /**
+ * highlight the search words provided in localstorage in the text
+ */
+ highlightSearchWords: () => {
+ if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight
+
+ // get and clear terms from localstorage
+ const url = new URL(window.location);
+ const highlight =
+ localStorage.getItem("sphinx_highlight_terms")
+ || url.searchParams.get("highlight")
+ || "";
+ localStorage.removeItem("sphinx_highlight_terms")
+ url.searchParams.delete("highlight");
+ window.history.replaceState({}, "", url);
+
+ // get individual terms from highlight string
+ const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
+ if (terms.length === 0) return; // nothing to do
+
+ // There should never be more than one element matching "div.body"
+ const divBody = document.querySelectorAll("div.body");
+ const body = divBody.length ? divBody[0] : document.querySelector("body");
+ window.setTimeout(() => {
+ terms.forEach((term) => _highlightText(body, term, "highlighted"));
+ }, 10);
+
+ const searchBox = document.getElementById("searchbox");
+ if (searchBox === null) return;
+ searchBox.appendChild(
+ document
+ .createRange()
+ .createContextualFragment(
+ '' +
+ '' +
+ _("Hide Search Matches") +
+ "
"
+ )
+ );
+ },
+
+ /**
+ * helper function to hide the search marks again
+ */
+ hideSearchWords: () => {
+ document
+ .querySelectorAll("#searchbox .highlight-link")
+ .forEach((el) => el.remove());
+ document
+ .querySelectorAll("span.highlighted")
+ .forEach((el) => el.classList.remove("highlighted"));
+ localStorage.removeItem("sphinx_highlight_terms")
+ },
+
+ initEscapeListener: () => {
+ // only install a listener if it is really needed
+ if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return;
+
+ document.addEventListener("keydown", (event) => {
+ // bail for input elements
+ if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
+ // bail with special keys
+ if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;
+ if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) {
+ SphinxHighlight.hideSearchWords();
+ event.preventDefault();
+ }
+ });
+ },
+};
+
+_ready(() => {
+ /* Do not call highlightSearchWords() when we are on the search page.
+ * It will highlight words from the *previous* search query.
+ */
+ if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords();
+ SphinxHighlight.initEscapeListener();
+});
diff --git a/singlehtml/_static/sphinx_lesson.css b/singlehtml/_static/sphinx_lesson.css
new file mode 100644
index 0000000..68cb32d
--- /dev/null
+++ b/singlehtml/_static/sphinx_lesson.css
@@ -0,0 +1,103 @@
+/* sphinx_lesson.css
+ * https://webaim.org/resources/contrastchecker/?fcolor=00000&bcolor=FCE762
+ * */
+:root {
+ --sphinx-lesson-selection-bg-color: #fce762;
+ --sphinx-lesson-selection-fg-color: #000000;
+}
+
+/* https://webaim.org/resources/contrastchecker/?fcolor=FFFFFF&bcolor=745315
+ * when dark theme is selected the some themes use this attirbute
+ */
+html[data-theme='dark'], body[data-theme='dark'] {
+ --sphinx-lesson-selection-bg-color: #745315;
+ --sphinx-lesson-selection-fg-color: #ffffff;
+}
+
+/* when browser/system theme is dark and no theme is selected */
+@media (prefers-color-scheme: dark) {
+ html[data-theme='auto'], body[data-theme='auto'] {
+ --sphinx-lesson-selection-bg-color: #745315;
+ --sphinx-lesson-selection-fg-color: #ffffff;
+ }
+}
+
+body.wy-body-for-nav img.with-border {
+ border: 2px solid;
+}
+
+.rst-content .admonition-no-content {
+ padding-bottom: 0px;
+}
+
+.rst-content .demo > .admonition-title::before {
+ content: "\01F440"; /* Eyes */ }
+.rst-content .type-along > .admonition-title::before {
+ content: "\02328\0FE0F"; /* Keyboard */ }
+.rst-content .exercise > .admonition-title::before {
+ content: "\0270D\0FE0F"; /* Hand */ }
+.rst-content .solution > .admonition-title::before {
+ content: "\02714\0FE0E"; /* Check mark */ }
+.rst-content .homework > .admonition-title::before {
+ content: "\01F4DD"; /* Memo */ }
+.rst-content .discussion > .admonition-title::before {
+ content: "\01F4AC"; /* Speech balloon */ }
+.rst-content .questions > .admonition-title::before {
+ content: "\02753\0FE0E"; /* Question mark */ }
+.rst-content .prerequisites > .admonition-title::before {
+ content: "\02699"; /* Gear */ }
+.rst-content .seealso > .admonition-title::before {
+ content: "\027A1\0FE0E"; /* Question mark */ }
+
+
+/* instructor-note */
+.rst-content .instructor-note {
+ background: #e7e7e7;
+}
+.rst-content .instructor-note > .admonition-title {
+ background: #6a6a6a;
+}
+.rst-content .instructor-note > .admonition-title::before {
+ content: "";
+}
+
+
+/* sphinx_toggle_button, make the font white */
+.rst-content .toggle.admonition button.toggle-button {
+ color: white;
+}
+
+/* sphinx-togglebutton, remove underflow when toggled to hidden mode */
+.rst-content .admonition.toggle-hidden {
+ padding-bottom: 0px;
+}
+
+/* selection / highlight colour uses a yellow background and a black text */
+/*** Works on common browsers ***/
+::selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
+
+/*** Mozilla based browsers ***/
+::-moz-selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
+
+/***For Other Browsers ***/
+::-o-selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
+
+::-ms-selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
+
+/*** For Webkit ***/
+::-webkit-selection {
+ background-color: var(--sphinx-lesson-selection-bg-color);
+ color: var(--sphinx-lesson-selection-fg-color);
+}
diff --git a/singlehtml/_static/sphinx_rtd_theme_ext_color_contrast.css b/singlehtml/_static/sphinx_rtd_theme_ext_color_contrast.css
new file mode 100644
index 0000000..e68feb8
--- /dev/null
+++ b/singlehtml/_static/sphinx_rtd_theme_ext_color_contrast.css
@@ -0,0 +1,47 @@
+/* The following are for web accessibility of sphinx_rtd_theme: they
+ * solve some of the most frequent contrast issues. Remove when this
+ * solved:
+ * https://github.com/readthedocs/sphinx_rtd_theme/issues/971
+ */
+/* background: #fcfcfc, note boxes #E7F2FA */
+a { color: #2573A7; } /* original #2980B9, #1F5C84; */
+body { color: #242424; } /* original #404040, #383838 */
+.wy-side-nav-search>a, .wy-side-nav-search .wy-dropdown>a {
+ color: #ffffff;
+} /* original #fcfcfc */
+footer { color: #737373; } /* original gray=#808080*/
+footer span.commit code, footer span.commit .rst-content tt, .rst-content footer span.commit tt {
+ color: #737373;
+} /* original gray=#808080*/
+.rst-content tt.literal, .rst-content tt.literal, .rst-content code.literal {
+ color: #AB2314;
+}
+/* Sidebar background */
+.wy-side-nav-search { background-color: #277CB4;}
+
+/* Same, but for pygments */
+.highlight .ch { color: #3E7A89; } /* #! line */
+.highlight .c1 { color: #3E7A89; } /* also comments */
+.highlight .nv { color: #AD3ECC; } /* variable */
+.highlight .gp { color: #B45608; } /* prompt character, $*/
+.highlight .si { color: #3975B1; } /* ${} variable text */
+.highlight .nc { color: #0C78A7; }
+
+/* Sphinx admonitions */
+/* warning */
+.wy-alert.wy-alert-warning .wy-alert-title, .rst-content .wy-alert-warning.note .wy-alert-title, .rst-content .attention .wy-alert-title, .rst-content .caution .wy-alert-title, .rst-content .wy-alert-warning.danger .wy-alert-title, .rst-content .wy-alert-warning.error .wy-alert-title, .rst-content .wy-alert-warning.hint .wy-alert-title, .rst-content .wy-alert-warning.important .wy-alert-title, .rst-content .wy-alert-warning.tip .wy-alert-title, .rst-content .warning .wy-alert-title, .rst-content .wy-alert-warning.seealso .wy-alert-title, .rst-content .admonition-todo .wy-alert-title, .rst-content .wy-alert-warning.admonition .wy-alert-title, .wy-alert.wy-alert-warning .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-warning .admonition-title, .rst-content .wy-alert-warning.note .admonition-title, .rst-content .attention .admonition-title, .rst-content .caution .admonition-title, .rst-content .wy-alert-warning.danger .admonition-title, .rst-content .wy-alert-warning.error .admonition-title, .rst-content .wy-alert-warning.hint .admonition-title, .rst-content .wy-alert-warning.important .admonition-title, .rst-content .wy-alert-warning.tip .admonition-title, .rst-content .warning .admonition-title, .rst-content .wy-alert-warning.seealso .admonition-title, .rst-content .admonition-todo .admonition-title, .rst-content .wy-alert-warning.admonition .admonition-title {
+ background: #B15E16; }
+/* important */
+.wy-alert.wy-alert-success .wy-alert-title, .rst-content .wy-alert-success.note .wy-alert-title, .rst-content .wy-alert-success.attention .wy-alert-title, .rst-content .wy-alert-success.caution .wy-alert-title, .rst-content .wy-alert-success.danger .wy-alert-title, .rst-content .wy-alert-success.error .wy-alert-title, .rst-content .hint .wy-alert-title, .rst-content .important .wy-alert-title, .rst-content .tip .wy-alert-title, .rst-content .wy-alert-success.warning .wy-alert-title, .rst-content .wy-alert-success.seealso .wy-alert-title, .rst-content .wy-alert-success.admonition-todo .wy-alert-title, .rst-content .wy-alert-success.admonition .wy-alert-title, .wy-alert.wy-alert-success .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-success .admonition-title, .rst-content .wy-alert-success.note .admonition-title, .rst-content .wy-alert-success.attention .admonition-title, .rst-content .wy-alert-success.caution .admonition-title, .rst-content .wy-alert-success.danger .admonition-title, .rst-content .wy-alert-success.error .admonition-title, .rst-content .hint .admonition-title, .rst-content .important .admonition-title, .rst-content .tip .admonition-title, .rst-content .wy-alert-success.warning .admonition-title, .rst-content .wy-alert-success.seealso .admonition-title, .rst-content .wy-alert-success.admonition-todo .admonition-title, .rst-content .wy-alert-success.admonition .admonition-title {
+ background: #12826C; }
+/* seealso, note, etc */
+.wy-alert.wy-alert-info .wy-alert-title, .rst-content .note .wy-alert-title, .rst-content .wy-alert-info.attention .wy-alert-title, .rst-content .wy-alert-info.caution .wy-alert-title, .rst-content .wy-alert-info.danger .wy-alert-title, .rst-content .wy-alert-info.error .wy-alert-title, .rst-content .wy-alert-info.hint .wy-alert-title, .rst-content .wy-alert-info.important .wy-alert-title, .rst-content .wy-alert-info.tip .wy-alert-title, .rst-content .wy-alert-info.warning .wy-alert-title, .rst-content .seealso .wy-alert-title, .rst-content .wy-alert-info.admonition-todo .wy-alert-title, .rst-content .wy-alert-info.admonition .wy-alert-title, .wy-alert.wy-alert-info .rst-content .admonition-title, .rst-content .wy-alert.wy-alert-info .admonition-title, .rst-content .note .admonition-title, .rst-content .wy-alert-info.attention .admonition-title, .rst-content .wy-alert-info.caution .admonition-title, .rst-content .wy-alert-info.danger .admonition-title, .rst-content .wy-alert-info.error .admonition-title, .rst-content .wy-alert-info.hint .admonition-title, .rst-content .wy-alert-info.important .admonition-title, .rst-content .wy-alert-info.tip .admonition-title, .rst-content .wy-alert-info.warning .admonition-title, .rst-content .seealso .admonition-title, .rst-content .wy-alert-info.admonition-todo .admonition-title, .rst-content .wy-alert-info.admonition .admonition-title {
+ background: #277CB4; }
+/* error, danger */
+.rst-content .danger .admonition-title, .rst-content .danger .wy-alert-title, .rst-content .error .admonition-title, .rst-content .error .wy-alert-title, .rst-content .wy-alert-danger.admonition-todo .admonition-title, .rst-content .wy-alert-danger.admonition-todo .wy-alert-title, .rst-content .wy-alert-danger.admonition .admonition-title, .rst-content .wy-alert-danger.admonition .wy-alert-title, .rst-content .wy-alert-danger.attention .admonition-title, .rst-content .wy-alert-danger.attention .wy-alert-title, .rst-content .wy-alert-danger.caution .admonition-title, .rst-content .wy-alert-danger.caution .wy-alert-title, .rst-content .wy-alert-danger.hint .admonition-title, .rst-content .wy-alert-danger.hint .wy-alert-title, .rst-content .wy-alert-danger.important .admonition-title, .rst-content .wy-alert-danger.important .wy-alert-title, .rst-content .wy-alert-danger.note .admonition-title, .rst-content .wy-alert-danger.note .wy-alert-title, .rst-content .wy-alert-danger.seealso .admonition-title, .rst-content .wy-alert-danger.seealso .wy-alert-title, .rst-content .wy-alert-danger.tip .admonition-title, .rst-content .wy-alert-danger.tip .wy-alert-title, .rst-content .wy-alert-danger.warning .admonition-title, .rst-content .wy-alert-danger.warning .wy-alert-title, .rst-content .wy-alert.wy-alert-danger .admonition-title, .wy-alert.wy-alert-danger .rst-content .admonition-title, .wy-alert.wy-alert-danger .wy-alert-title {
+ background: #e31704;
+}
+
+/* Generic admonition titles */
+.wy-alert-title, .rst-content .admonition-title {
+ background: #277CB4; }
diff --git a/singlehtml/_static/style.css b/singlehtml/_static/style.css
new file mode 100644
index 0000000..59e60ee
--- /dev/null
+++ b/singlehtml/_static/style.css
@@ -0,0 +1,6 @@
+.rst-content .objectives {
+ background: #fee0d2;
+}
+.rst-content .objectives > .admonition-title {
+ background: #fc9272;
+}
diff --git a/singlehtml/_static/tabs.css b/singlehtml/_static/tabs.css
new file mode 100644
index 0000000..957ba60
--- /dev/null
+++ b/singlehtml/_static/tabs.css
@@ -0,0 +1,89 @@
+.sphinx-tabs {
+ margin-bottom: 1rem;
+}
+
+[role="tablist"] {
+ border-bottom: 1px solid #a0b3bf;
+}
+
+.sphinx-tabs-tab {
+ position: relative;
+ font-family: Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;
+ color: #1D5C87;
+ line-height: 24px;
+ margin: 0;
+ font-size: 16px;
+ font-weight: 400;
+ background-color: rgba(255, 255, 255, 0);
+ border-radius: 5px 5px 0 0;
+ border: 0;
+ padding: 1rem 1.5rem;
+ margin-bottom: 0;
+}
+
+.sphinx-tabs-tab[aria-selected="true"] {
+ font-weight: 700;
+ border: 1px solid #a0b3bf;
+ border-bottom: 1px solid white;
+ margin: -1px;
+ background-color: white;
+}
+
+.sphinx-tabs-tab:focus {
+ z-index: 1;
+ outline-offset: 1px;
+}
+
+.sphinx-tabs-panel {
+ position: relative;
+ padding: 1rem;
+ border: 1px solid #a0b3bf;
+ margin: 0px -1px -1px -1px;
+ border-radius: 0 0 5px 5px;
+ border-top: 0;
+ background: white;
+}
+
+.sphinx-tabs-panel.code-tab {
+ padding: 0.4rem;
+}
+
+.sphinx-tab img {
+ margin-bottom: 24 px;
+}
+
+/* Dark theme preference styling */
+
+@media (prefers-color-scheme: dark) {
+ body[data-theme="auto"] .sphinx-tabs-panel {
+ color: white;
+ background-color: rgb(50, 50, 50);
+ }
+
+ body[data-theme="auto"] .sphinx-tabs-tab {
+ color: white;
+ background-color: rgba(255, 255, 255, 0.05);
+ }
+
+ body[data-theme="auto"] .sphinx-tabs-tab[aria-selected="true"] {
+ border-bottom: 1px solid rgb(50, 50, 50);
+ background-color: rgb(50, 50, 50);
+ }
+}
+
+/* Explicit dark theme styling */
+
+body[data-theme="dark"] .sphinx-tabs-panel {
+ color: white;
+ background-color: rgb(50, 50, 50);
+}
+
+body[data-theme="dark"] .sphinx-tabs-tab {
+ color: white;
+ background-color: rgba(255, 255, 255, 0.05);
+}
+
+body[data-theme="dark"] .sphinx-tabs-tab[aria-selected="true"] {
+ border-bottom: 2px solid rgb(50, 50, 50);
+ background-color: rgb(50, 50, 50);
+}
diff --git a/singlehtml/_static/tabs.js b/singlehtml/_static/tabs.js
new file mode 100644
index 0000000..48dc303
--- /dev/null
+++ b/singlehtml/_static/tabs.js
@@ -0,0 +1,145 @@
+try {
+ var session = window.sessionStorage || {};
+} catch (e) {
+ var session = {};
+}
+
+window.addEventListener("DOMContentLoaded", () => {
+ const allTabs = document.querySelectorAll('.sphinx-tabs-tab');
+ const tabLists = document.querySelectorAll('[role="tablist"]');
+
+ allTabs.forEach(tab => {
+ tab.addEventListener("click", changeTabs);
+ });
+
+ tabLists.forEach(tabList => {
+ tabList.addEventListener("keydown", keyTabs);
+ });
+
+ // Restore group tab selection from session
+ const lastSelected = session.getItem('sphinx-tabs-last-selected');
+ if (lastSelected != null) selectNamedTabs(lastSelected);
+});
+
+/**
+ * Key focus left and right between sibling elements using arrows
+ * @param {Node} e the element in focus when key was pressed
+ */
+function keyTabs(e) {
+ const tab = e.target;
+ let nextTab = null;
+ if (e.keyCode === 39 || e.keyCode === 37) {
+ tab.setAttribute("tabindex", -1);
+ // Move right
+ if (e.keyCode === 39) {
+ nextTab = tab.nextElementSibling;
+ if (nextTab === null) {
+ nextTab = tab.parentNode.firstElementChild;
+ }
+ // Move left
+ } else if (e.keyCode === 37) {
+ nextTab = tab.previousElementSibling;
+ if (nextTab === null) {
+ nextTab = tab.parentNode.lastElementChild;
+ }
+ }
+ }
+
+ if (nextTab !== null) {
+ nextTab.setAttribute("tabindex", 0);
+ nextTab.focus();
+ }
+}
+
+/**
+ * Select or deselect clicked tab. If a group tab
+ * is selected, also select tab in other tabLists.
+ * @param {Node} e the element that was clicked
+ */
+function changeTabs(e) {
+ // Use this instead of the element that was clicked, in case it's a child
+ const notSelected = this.getAttribute("aria-selected") === "false";
+ const positionBefore = this.parentNode.getBoundingClientRect().top;
+ const notClosable = !this.parentNode.classList.contains("closeable");
+
+ deselectTabList(this);
+
+ if (notSelected || notClosable) {
+ selectTab(this);
+ const name = this.getAttribute("name");
+ selectNamedTabs(name, this.id);
+
+ if (this.classList.contains("group-tab")) {
+ // Persist during session
+ session.setItem('sphinx-tabs-last-selected', name);
+ }
+ }
+
+ const positionAfter = this.parentNode.getBoundingClientRect().top;
+ const positionDelta = positionAfter - positionBefore;
+ // Scroll to offset content resizing
+ window.scrollTo(0, window.scrollY + positionDelta);
+}
+
+/**
+ * Select tab and show associated panel.
+ * @param {Node} tab tab to select
+ */
+function selectTab(tab) {
+ tab.setAttribute("aria-selected", true);
+
+ // Show the associated panel
+ document
+ .getElementById(tab.getAttribute("aria-controls"))
+ .removeAttribute("hidden");
+}
+
+/**
+ * Hide the panels associated with all tabs within the
+ * tablist containing this tab.
+ * @param {Node} tab a tab within the tablist to deselect
+ */
+function deselectTabList(tab) {
+ const parent = tab.parentNode;
+ const grandparent = parent.parentNode;
+
+ Array.from(parent.children)
+ .forEach(t => t.setAttribute("aria-selected", false));
+
+ Array.from(grandparent.children)
+ .slice(1) // Skip tablist
+ .forEach(panel => panel.setAttribute("hidden", true));
+}
+
+/**
+ * Select grouped tabs with the same name, but no the tab
+ * with the given id.
+ * @param {Node} name name of grouped tab to be selected
+ * @param {Node} clickedId id of clicked tab
+ */
+function selectNamedTabs(name, clickedId=null) {
+ const groupedTabs = document.querySelectorAll(`.sphinx-tabs-tab[name="${name}"]`);
+ const tabLists = Array.from(groupedTabs).map(tab => tab.parentNode);
+
+ tabLists
+ .forEach(tabList => {
+ // Don't want to change the tabList containing the clicked tab
+ const clickedTab = tabList.querySelector(`[id="${clickedId}"]`);
+ if (clickedTab === null ) {
+ // Select first tab with matching name
+ const tab = tabList.querySelector(`.sphinx-tabs-tab[name="${name}"]`);
+ deselectTabList(tab);
+ selectTab(tab);
+ }
+ })
+}
+
+if (typeof exports === 'undefined') {
+ exports = {};
+}
+
+exports.keyTabs = keyTabs;
+exports.changeTabs = changeTabs;
+exports.selectTab = selectTab;
+exports.deselectTabList = deselectTabList;
+exports.selectNamedTabs = selectNamedTabs;
diff --git a/singlehtml/_static/term_role_formatting.css b/singlehtml/_static/term_role_formatting.css
new file mode 100644
index 0000000..0b66095
--- /dev/null
+++ b/singlehtml/_static/term_role_formatting.css
@@ -0,0 +1,4 @@
+/* Make terms bold */
+a.reference span.std-term {
+ font-weight: bold;
+}
diff --git a/singlehtml/_static/togglebutton.css b/singlehtml/_static/togglebutton.css
new file mode 100644
index 0000000..54a6787
--- /dev/null
+++ b/singlehtml/_static/togglebutton.css
@@ -0,0 +1,160 @@
+/**
+ * Admonition-based toggles
+ */
+
+/* Visibility of the target */
+.admonition.toggle .admonition-title ~ * {
+ transition: opacity .3s, height .3s;
+}
+
+/* Toggle buttons inside admonitions so we see the title */
+.admonition.toggle {
+ position: relative;
+}
+
+/* Titles should cut off earlier to avoid overlapping w/ button */
+.admonition.toggle .admonition-title {
+ padding-right: 25%;
+ cursor: pointer;
+}
+
+/* Hovering will cause a slight shift in color to make it feel interactive */
+.admonition.toggle .admonition-title:hover {
+ box-shadow: inset 0 0 0px 20px rgb(0 0 0 / 1%);
+}
+
+/* Hovering will cause a slight shift in color to make it feel interactive */
+.admonition.toggle .admonition-title:active {
+ box-shadow: inset 0 0 0px 20px rgb(0 0 0 / 3%);
+}
+
+/* Remove extra whitespace below the admonition title when hidden */
+.admonition.toggle-hidden {
+ padding-bottom: 0;
+}
+
+.admonition.toggle-hidden .admonition-title {
+ margin-bottom: 0;
+}
+
+/* hides all the content of a page until de-toggled */
+.admonition.toggle-hidden .admonition-title ~ * {
+ height: 0;
+ margin: 0;
+ opacity: 0;
+ visibility: hidden;
+}
+
+/* General button style and position*/
+button.toggle-button {
+ /**
+ * Background and shape. By default there's no background
+ * but users can style as they wish
+ */
+ background: none;
+ border: none;
+ outline: none;
+
+ /* Positioning just inside the admonition title */
+ position: absolute;
+ right: 0.5em;
+ padding: 0px;
+ border: none;
+ outline: none;
+}
+
+/* Display the toggle hint on wide screens */
+@media (min-width: 768px) {
+ button.toggle-button.toggle-button-hidden:before {
+ content: attr(data-toggle-hint); /* This will be filled in by JS */
+ font-size: .8em;
+ align-self: center;
+ }
+}
+
+/* Icon behavior */
+.tb-icon {
+ transition: transform .2s ease-out;
+ height: 1.5em;
+ width: 1.5em;
+ stroke: currentColor; /* So that we inherit the color of other text */
+}
+
+/* The icon should point right when closed, down when open. */
+/* Open */
+.admonition.toggle button .tb-icon {
+ transform: rotate(90deg);
+}
+
+/* Closed */
+.admonition.toggle button.toggle-button-hidden .tb-icon {
+ transform: rotate(0deg);
+}
+
+/* With details toggles, we don't rotate the icon so it points right */
+details.toggle-details .tb-icon {
+ height: 1.4em;
+ width: 1.4em;
+ margin-top: 0.1em; /* To center the button vertically */
+}
+
+
+/**
+ * Details-based toggles.
+ * In this case, we wrap elements with `.toggle` in a details block.
+ */
+
+/* Details blocks */
+details.toggle-details {
+ margin: 1em 0;
+}
+
+
+details.toggle-details summary {
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ list-style: none;
+ border-radius: .2em;
+ border-left: 3px solid #1976d2;
+ background-color: rgb(204 204 204 / 10%);
+ padding: 0.2em 0.7em 0.3em 0.5em; /* Less padding on left because the SVG has left margin */
+ font-size: 0.9em;
+}
+
+details.toggle-details summary:hover {
+ background-color: rgb(204 204 204 / 20%);
+}
+
+details.toggle-details summary:active {
+ background: rgb(204 204 204 / 28%);
+}
+
+.toggle-details__summary-text {
+ margin-left: 0.2em;
+}
+
+details.toggle-details[open] summary {
+ margin-bottom: .5em;
+}
+
+details.toggle-details[open] summary .tb-icon {
+ transform: rotate(90deg);
+}
+
+details.toggle-details[open] summary ~ * {
+ animation: toggle-fade-in .3s ease-out;
+}
+
+@keyframes toggle-fade-in {
+ from {opacity: 0%;}
+ to {opacity: 100%;}
+}
+
+/* Print rules - we hide all toggle button elements at print */
+@media print {
+ /* Always hide the summary so the button doesn't show up */
+ details.toggle-details summary {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/singlehtml/_static/togglebutton.js b/singlehtml/_static/togglebutton.js
new file mode 100644
index 0000000..215a7ee
--- /dev/null
+++ b/singlehtml/_static/togglebutton.js
@@ -0,0 +1,187 @@
+/**
+ * Add Toggle Buttons to elements
+ */
+
+let toggleChevron = `
+
+
+
+ `;
+
+var initToggleItems = () => {
+ var itemsToToggle = document.querySelectorAll(togglebuttonSelector);
+ console.log(`[togglebutton]: Adding toggle buttons to ${itemsToToggle.length} items`)
+ // Add the button to each admonition and hook up a callback to toggle visibility
+ itemsToToggle.forEach((item, index) => {
+ if (item.classList.contains("admonition")) {
+ // If it's an admonition block, then we'll add a button inside
+ // Generate unique IDs for this item
+ var toggleID = `toggle-${index}`;
+ var buttonID = `button-${toggleID}`;
+
+ item.setAttribute('id', toggleID);
+ if (!item.classList.contains("toggle")){
+ item.classList.add("toggle");
+ }
+ // This is the button that will be added to each item to trigger the toggle
+ var collapseButton = `
+
+ ${toggleChevron}
+ `;
+
+ title = item.querySelector(".admonition-title")
+ title.insertAdjacentHTML("beforeend", collapseButton);
+ thisButton = document.getElementById(buttonID);
+
+ // Add click handlers for the button + admonition title (if admonition)
+ admonitionTitle = document.querySelector(`#${toggleID} > .admonition-title`)
+ if (admonitionTitle) {
+ // If an admonition, then make the whole title block clickable
+ admonitionTitle.addEventListener('click', toggleClickHandler);
+ admonitionTitle.dataset.target = toggleID
+ admonitionTitle.dataset.button = buttonID
+ } else {
+ // If not an admonition then we'll listen for the button click
+ thisButton.addEventListener('click', toggleClickHandler);
+ }
+
+ // Now hide the item for this toggle button unless explicitly noted to show
+ if (!item.classList.contains("toggle-shown")) {
+ toggleHidden(thisButton);
+ }
+ } else {
+ // If not an admonition, wrap the block in a block
+ // Define the structure of the details block and insert it as a sibling
+ var detailsBlock = `
+
+
+ ${toggleChevron}
+ ${toggleHintShow}
+
+ `;
+ item.insertAdjacentHTML("beforebegin", detailsBlock);
+
+ // Now move the toggle-able content inside of the details block
+ details = item.previousElementSibling
+ details.appendChild(item)
+ item.classList.add("toggle-details__container")
+
+ // Set up a click trigger to change the text as needed
+ details.addEventListener('click', (click) => {
+ let parent = click.target.parentElement;
+ if (parent.tagName.toLowerCase() == "details") {
+ summary = parent.querySelector("summary");
+ details = parent;
+ } else {
+ summary = parent;
+ details = parent.parentElement;
+ }
+ // Update the inner text for the proper hint
+ if (details.open) {
+ summary.querySelector("span.toggle-details__summary-text").innerText = toggleHintShow;
+ } else {
+ summary.querySelector("span.toggle-details__summary-text").innerText = toggleHintHide;
+ }
+
+ });
+
+ // If we have a toggle-shown class, open details block should be open
+ if (item.classList.contains("toggle-shown")) {
+ details.click();
+ }
+ }
+ })
+};
+
+// This should simply add / remove the collapsed class and change the button text
+var toggleHidden = (button) => {
+ target = button.dataset['target']
+ var itemToToggle = document.getElementById(target);
+ if (itemToToggle.classList.contains("toggle-hidden")) {
+ itemToToggle.classList.remove("toggle-hidden");
+ button.classList.remove("toggle-button-hidden");
+ } else {
+ itemToToggle.classList.add("toggle-hidden");
+ button.classList.add("toggle-button-hidden");
+ }
+}
+
+var toggleClickHandler = (click) => {
+ // Be cause the admonition title is clickable and extends to the whole admonition
+ // We only look for a click event on this title to trigger the toggle.
+
+ if (click.target.classList.contains("admonition-title")) {
+ button = click.target.querySelector(".toggle-button");
+ } else if (click.target.classList.contains("tb-icon")) {
+ // We've clicked the icon and need to search up one parent for the button
+ button = click.target.parentElement;
+ } else if (click.target.tagName == "polyline") {
+ // We've clicked the SVG elements inside the button, need to up 2 layers
+ button = click.target.parentElement.parentElement;
+ } else if (click.target.classList.contains("toggle-button")) {
+ // We've clicked the button itself and so don't need to do anything
+ button = click.target;
+ } else {
+ console.log(`[togglebutton]: Couldn't find button for ${click.target}`)
+ }
+ target = document.getElementById(button.dataset['button']);
+ toggleHidden(target);
+}
+
+// If we want to blanket-add toggle classes to certain cells
+var addToggleToSelector = () => {
+ const selector = "";
+ if (selector.length > 0) {
+ document.querySelectorAll(selector).forEach((item) => {
+ item.classList.add("toggle");
+ })
+ }
+}
+
+// Helper function to run when the DOM is finished
+const sphinxToggleRunWhenDOMLoaded = cb => {
+ if (document.readyState != 'loading') {
+ cb()
+ } else if (document.addEventListener) {
+ document.addEventListener('DOMContentLoaded', cb)
+ } else {
+ document.attachEvent('onreadystatechange', function() {
+ if (document.readyState == 'complete') cb()
+ })
+ }
+}
+sphinxToggleRunWhenDOMLoaded(addToggleToSelector)
+sphinxToggleRunWhenDOMLoaded(initToggleItems)
+
+/** Toggle details blocks to be open when printing */
+if (toggleOpenOnPrint == "true") {
+ window.addEventListener("beforeprint", () => {
+ // Open the details
+ document.querySelectorAll("details.toggle-details").forEach((el) => {
+ el.dataset["togglestatus"] = el.open;
+ el.open = true;
+ });
+
+ // Open the admonitions
+ document.querySelectorAll(".admonition.toggle.toggle-hidden").forEach((el) => {
+ console.log(el);
+ el.querySelector("button.toggle-button").click();
+ el.dataset["toggle_after_print"] = "true";
+ });
+ });
+ window.addEventListener("afterprint", () => {
+ // Re-close the details that were closed
+ document.querySelectorAll("details.toggle-details").forEach((el) => {
+ el.open = el.dataset["togglestatus"] == "true";
+ delete el.dataset["togglestatus"];
+ });
+
+ // Re-close the admonition toggle buttons
+ document.querySelectorAll(".admonition.toggle").forEach((el) => {
+ if (el.dataset["toggle_after_print"] == "true") {
+ el.querySelector("button.toggle-button").click();
+ delete el.dataset["toggle_after_print"];
+ }
+ });
+ });
+}
diff --git a/singlehtml/index.html b/singlehtml/index.html
new file mode 100644
index 0000000..abcce9b
--- /dev/null
+++ b/singlehtml/index.html
@@ -0,0 +1,5453 @@
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example) documentation
+
+ Edit on GitHub
+
+
+
+
+
+
+
+
+Reproducible research software development using Python (ML example)
+
+Big-picture goal
+This is a hands-on course on research software engineering . In this
+workshop we assume that most workshop participants use Python in their work or
+are leading a group which uses Python. Therefore, some of the examples will use
+Python as the example language.
+We will work with an example project (Example project: 2D classification task using a nearest-neighbor predictor )
+and go through all important steps of a typical
+software project. Once we have seen the building blocks, we will try to apply
+them to own projects .
+
+
Preparation
+
+Get a GitHub account following these instructions .
+You will need a text editor . If you don’t have a favorite one, we recommend
+VS Code .
+If you prefer to work in the terminal and not in VS Code, set up these two (skip this if you use VS Code):
+
+
+Follow Software install instructions (but we will do this together at the beginning of the workshop).
+
+
+
+
+Schedule
+
+
+
+Wednesday
+
+09:00-10:00 - Automated testing
+10:15-11:30 - Modular code development
+
+
+11:30-12:15 - Lunch break
+12:15-14:00 - How to release and publish your code
+
+
+14:15-15:00 - Debriefing and Q&A
+
+Participants work on their projects
+Together we study actual codes that participants wrote or work on
+Constructively we discuss possible improvements
+Give individual feedback on code projects
+
+
+
+
+
+Thursday
+
+
+
+Software install instructions
+[this page is adapted from https://aaltoscicomp.github.io/python-for-scicomp/installation/ ]
+
+Choosing an installation method
+For this course we will install an isolated environment
+with following dependencies:
+
+
environment.yml requirements.txt
name : course
+channels :
+ - conda-forge
+ - bioconda
+dependencies :
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - jupyterlab
+ - pytest
+ - scalene
+ - flit
+ - ruff
+ - icecream
+ - snakemake-minimal
+ - myst-parser
+ - sphinx
+ - sphinx-rtd-theme
+ - sphinx-autoapi
+ - sphinx-autobuild
+ - black
+ - isort
+ - pip
+ - pip :
+ - jupyterlab-code-formatter
+
+
+
click
+numpy
+pandas
+scipy
+altair
+vl - convert - python
+jupyterlab
+pytest
+scalene
+flit
+ruff
+icecream
+myst - parser
+sphinx
+sphinx - rtd - theme
+sphinx - autoapi
+sphinx - autobuild
+black
+isort
+jupyterlab - code - formatter
+
+
+
+
+If you are new to Python or unsure how to create isolated environments in
+Python from files above, please follow the
+instructions below.
+
+
There are many choices and we try to suggest a good compromise
+
There are very many ways to install Python and packages with pros and cons and
+in addition there are several operating systems with their own quirks. This
+can be a huge challenge for beginners to navigate. It can also difficult for
+instructors to give recommendations for something which will work everywhere
+and which everybody will like.
+
Below we will recommend Miniforge since it is free, open source, general,
+available on all operating systems, and provides a good basis for reproducible
+environments. However, it does not provide a graphical user interface during
+installation. This means that every time we want to start a JupyterLab session,
+we will have to go through the command line.
+
+
+
Python, conda, anaconda, miniforge, etc?
+
Unfortunately there are many options and a lot of jargon.
+Here is a crash course:
+
+Python is a programming language very commonly used in
+science, it’s the topic of this course.
+Conda is a package manager: it allows distributing and
+installing packages, and is designed for complex scientific
+code.
+Mamba is a re-implementation of Conda to be much faster with
+resolving dependencies and installing things.
+An environment is a self-contained collections of packages
+which can be installed separately from others. They are used so
+each project can install what it needs without affecting others.
+Anaconda is a commercial distribution of Python+Conda+many
+packages that all work together. It used to be freely usable for
+research, but since ~2023-2024 it’s more limited. Thus, we don’t
+recommend it (even though it has a nice graphical user interface).
+conda-forge is another channel of distributing packages that
+is maintained by the community, and thus can be used by anyone.
+(Anaconda’s parent company also hosts conda-forge packages)
+Miniforge is a distribution of conda pre-configured for
+conda-forge. It operates via the command line.
+Miniconda is a distribution of conda pre-configured to use
+the Anaconda channels.
+
+
We will gain a better background and overview in the section
+Reproducible environments and dependencies .
+
+
+
+Installing Python via Miniforge
+Follow the instructions on the miniforge web page . This installs
+the base, and from here other packages can be installed.
+Unsure what to download and what to do with it?
+
+
Windows MacOS Linux
You want to download and run Miniforge3-Windows-x86_64.exe
.
+
You probably want to download the Miniforge3-MacOSX-arm64.sh
file (unless
+you don’t have an Arm processor) and then run it in the terminal with:
+
$ bash Miniforge3-MacOSX-arm64.sh
+
+
+
You probably want to download the Miniforge3-Linux-x86_64.sh
file (unless
+you don’t have an x86_64 processor) and then run it in the terminal with:
+
$ bash Miniforge3-Linux-x86_64.sh
+
+
+
+
+
+Installing and activating the software environment
+First we will start Python in a way that activates conda/mamba. Then we will
+install the software environment from this environment.yml
+file .
+An environment is a self-contained set of extra libraries - different
+projects can use different environments to not interfere with each other. This
+environment will have all of the software needed for this particular course.
+We will call the environment course
.
+
+
Windows Linux / MacOS
Use the “Miniforge Prompt” to start Miniforge. This
+will set up everything so that conda
and mamba
are
+available.
+Then type
+(without the $
):
+
$ mamba env create -n course -f https://raw.githubusercontent.com/coderefinery/reproducible-python-ml/main/software/environment.yml
+
+
+
Each time you start a new command line terminal,
+you can activate Miniforge by running
+(without the $
):
+
$ source ~/miniforge3/bin/activate
+
+
+
This is needed so that
+Miniforge is usable wherever you need, but doesn’t affect any
+other software on your computer (this is not needed if you
+choose “Do you wish to update your shell profile to
+automatically initialize conda?”, but then it will always be
+active).
+
In the second step, we will install the software environment:
+
$ mamba env create -n course -f https://raw.githubusercontent.com/coderefinery/reproducible-python-ml/main/software/environment.yml
+
+
+
+
+
+Starting JupyterLab
+Every time we want to start a JupyterLab session,
+we will have to go through the command line and first
+activate the course
environment.
+
+
Windows Linux / MacOS
Start the Miniforge Prompt. Then type
+(without the $
):
+
$ conda activate course
+$ jupyter-lab
+
+
+
Start the terminal and in the terminal, type
+(without the $
):
+
$ source ~/miniforge3/bin/activate
+$ conda activate course
+$ jupyter-lab
+
+
+
+
+
+Removing the software environment
+
+
Windows Linux / MacOS
In the Miniforge Prompt, type
+(without the $
):
+
$ conda env list
+$ conda env remove --name course
+$ conda env list
+
+
+
In the terminal, type
+(without the $
):
+
$ source ~/miniforge3/bin/activate
+$ conda env list
+$ conda env remove --name course
+$ conda env list
+
+
+
+
+
+How to verify your installation
+Start JupyterLab (as described above). It will hopefully open up your browser
+and look like this:
+
+
+
+
+JupyterLab opened in the browser. Click on the Python 3 tile.
+
+
+Once you clicked the Python 3 tile it should look like this:
+
+
+
+
+Python 3 notebook started.
+
+
+Into that blue “cell” please type the following:
+import altair
+import pandas
+
+print ( "all good - ready for the course" )
+
+
+
+
+
+
+Please copy these lines and click on the “play”/”run” icon.
+
+
+This is how it should look:
+
+
+
+
+Screenshot after successful import.
+
+
+If this worked, you are all set and can close JupyterLab (no need to save these
+changes).
+This is how it should not look:
+
+
+
+
+Error: required packages could not be found.
+
+
+
+
+
+
+
+Example project: 2D classification task using a nearest-neighbor predictor
+The example code
+that we will study is a relatively simple nearest-neighbor predictor written in
+Python. It is not important or expected that we understand the code in detail.
+The code will produce something like this:
+
+
+
+
+The bottom row shows the training data (two labels) and the top row shows the
+test data and whether the nearest-neighbor predictor classified their labels
+correctly.
+
+
+The big picture of the code is as follows:
+
+We can choose the number of samples (the example above has 50 samples).
+The code will generate samples with two labels (0 and 1) in a 2D space.
+One of the labels has a normal distribution and a circular distribution with
+some minimum and maximum radius.
+The second label only has a circular distribution with a different radius.
+Then we try to predict whether the test samples belong to label 0 or 1 based
+on the nearest neighbors in the training data. The number of neighbors can
+be adjusted and the code will take label of the majority of the neighbors.
+
+
+Example run
+
+
Instructor note
+
The instructor demonstrates running the code on their computer.
+
+The code is written to accept command-line arguments to specify the number
+of samples and file names. Later we will discuss advantages of this approach.
+Let us try to get the help text:
+$ python generate_data.py --help
+
+Usage: generate_data.py [OPTIONS]
+
+ Program that generates a set of training and test samples for a non-linear
+ classification task.
+
+Options:
+ --num-samples INTEGER Number of samples for each class. [required]
+ --training-data TEXT Training data is written to this file. [required]
+ --test-data TEXT Test data is written to this file. [required]
+ --help Show this message and exit.
+
+
+We first generate the training and test data:
+$ python generate_data.py --num-samples 50 --training-data train.csv --test-data test.csv
+
+Generated 50 training samples (train.csv) and test samples (test.csv).
+
+
+In a second step we generate predictions for the test data:
+$ python generate_predictions.py --num-neighbors 7 --training-data train.csv --test-data test.csv --predictions predictions.csv
+
+Predictions saved to predictions.csv
+
+
+Finally, we can plot the results:
+$ python plot_results.py --training-data train.csv --predictions predictions.csv --output-chart chart.svg
+
+Accuracy: 0.94
+Saved chart to chart.svg
+
+
+
+
+Discussion and goals
+
+
+
+
We will not focus on …
+
+… how the code works internally in detail.
+… whether this is the most efficient algorithm.
+… whether the code is numerically stable.
+… how to code scales with system size.
+… whether it is portable to other operating systems (we will discuss this later).
+
+
+
+
+
+Introduction to version control with Git and GitHub
+
+
+Motivation
+
+
Objectives
+
+Browse commits and branches of a Git repository.
+Remember that commits are like snapshots of the repository at a certain
+point in time.
+Know the difference between Git (something that tracks changes) and
+GitHub/GitLab (a web platform to host Git repositories).
+
+
+
+Why do we need to keep track of versions?
+Version control is an answer to the following questions (do you recognize some
+of them?):
+
+“It broke … hopefully I have a working version somewhere?”
+“Can you please send me the latest version?”
+“Where is the latest version?”
+“Which version are you using?”
+“Which version have the authors used in the paper I am trying to reproduce?”
+“Found a bug! Since when was it there?”
+“I am sure it used to work. When did it change?”
+“My laptop is gone. Is my thesis now gone?”
+
+
+
+Demonstration
+
+Example repository: https://github.com/workshop-material/classification-task
+Commits are like snapshots and if we break something we can go back to a
+previous snapshot.
+Commits carry metadata about changes: author, date, commit message, and
+a checksum.
+Branches are like parallel universes where you can experiment with
+changes without affecting the default branch:
+https://github.com/workshop-material/classification-task/network
+(“Insights” -> “Network”)
+With version control we can annotate code
+(example ).
+Collaboration : We can fork (make a copy on GitHub), clone (make a copy
+to our computer), review, compare, share, and discuss.
+Code review : Others can suggest changes using pull requests or merge
+requests. These can be reviewed and discussed before they are merged.
+Conceptually, they are similar to “suggesting changes” in Google Docs.
+
+
+
+
+
+What we typically like to snapshot
+
+Software (this is how it started but Git/GitHub can track a lot more)
+Scripts
+Documents (plain text files much better suitable than Word documents)
+Manuscripts (Git is great for collaborating/sharing LaTeX or Quarto manuscripts)
+Configuration files
+Website sources
+Data
+
+
+
Discussion
+
In this example somebody tried to keep track of versions without a version
+control system tool like Git. Discuss the following directory listing. What
+possible problems do you anticipate with this kind of “version control”:
+
myproject-2019.zip
+myproject-2020-february.zip
+myproject-2021-august.zip
+myproject-2023-09-19-working.zip
+myproject-2023-09-21.zip
+myproject-2023-09-21-test.zip
+myproject-2023-09-21-myversion.zip
+myproject-2023-09-21-newfeature.zip
+...
+(100 more files like these)
+
+
+
+
+
+
+
+Forking, cloning, and browsing
+In this episode, we will look at an existing repository to understand how
+all the pieces work together. Along the way, we will make a copy (by
+forking and/or cloning ) of the repository for us, which will be used
+for our own changes.
+
+
Objectives
+
+See a real Git repository and understand what is inside of it.
+Understand how version control allows advanced inspection of a
+repository.
+See how Git allows multiple people to work on the same project at the same time.
+See the big picture instead of remembering a bunch of commands.
+
+
+
+GitHub, VS Code, or command line
+We offer three different paths for this exercise:
+
+GitHub (this is the one we will demonstrate)
+VS Code (if you prefer to follow along using an editor)
+Command line (for people comfortable with the command line)
+
+
+
+Creating a copy of the repository by “forking” or “cloning”
+A repository is a collection of files in one directory tracked by Git. A
+GitHub repository is GitHub’s copy, which adds things like access control,
+issue tracking, and discussions. Each GitHub repository is owned by a user or
+organization, who controls access.
+First, we need to make our own copy of the exercise repository. This will
+become important later, when we make our own changes.
+
+
+
+
+Illustration of forking a repository on GitHub.
+
+
+
+
+
+
+Illustration of cloning a repository to your computer.
+
+
+
+
+
+
+It is also possible to do this: to clone a forked repository to your computer.
+
+
+At all times you should be aware of if you are looking at your repository
+or the upstream repository (original repository):
+
+
+
How to create a fork
+
+Go to the repository view on GitHub: https://github.com/workshop-material/classification-task
+First, on GitHub, click the button that says “Fork”. It is towards
+the top-right of the screen.
+You should shortly be redirected to your copy of the repository
+USER/classification-task .
+
+
+
+
Instructor note
+
Before starting the exercise session show
+how to fork the repository to own account
+(above).
+
+
+
+Exercise: Copy and browse an existing project
+Work on this by yourself or in pairs.
+
+
Exercise preparation
+
+
GitHub VS Code Command line
In this case you will work on a fork.
+
You only need to open your own view, as described above. The browser
+URL should look like https://github.com/USER /classification-task, where
+USER is your GitHub username.
+
You need to have forked the repository as described above.
+
We need to start by making a clone of this repository so that you can work
+locally.
+
+Start VS Code.
+If you don’t have the default view (you already have a project
+open), go to File → New Window.
+Under “Start” on the screen, select “Clone Git Repository…”. Alternatively
+navigate to the “Source Control” tab on the left sidebar and click on the “Clone Repository” button.
+Paste in this URL: https://github.com/USER/classification-task
, where
+USER
is your username. You can copy this from the browser.
+Browse and select the folder in which you want to clone the
+repository.
+Say yes, you want to open this repository.
+Select “Yes, I trust the authors” (the other option works too).
+
+
This path is advanced and we do not include all command line
+information: you need to be somewhat
+comfortable with the command line already.
+
You need to have forked the repository as described above.
+
We need to start by making a clone of this repository so that you can work
+locally.
+
+Start the terminal in which you use Git (terminal application, or
+Git Bash).
+Change to the directory where you would want the repository to be
+(cd ~/code
for example, if the ~/code
directory is where you
+store your files).
+Run the following command: git clone https://github.com/USER/classification-task
, where USER
is your
+username. You might need to use a SSH clone command instead of
+HTTPS, depending on your setup.
+Change to that directory: cd classification-task
+
+
+
+
+
Exercise: Browsing an existing project (20 min)
+
Browse the example project and
+explore commits and branches, either on a fork or on a clone. Take notes and
+prepare questions. The hints are for the GitHub path in the browser.
+
+Browse the commit history : Are commit messages understandable?
+(Hint: “Commit history”, the timeline symbol, above the file list)
+Compare the commit history with the network graph (“Insights” -> “Network”). Can you find the branches?
+Try to find the history of commits for a single file , e.g. generate_predictions.py
.
+(Hint: “History” button in the file view)
+Which files include the word “training” ?
+(Hint: the GitHub search on top of the repository view)
+In the generate_predictions.py
file,
+find out who modified the evaluation of “majority_index”
+last and in which commit .
+(Hint: “Blame” view in the file view)
+Can you use this code yourself? Are you allowed to share
+modifications ?
+(Hint: look for a license file)
+
+
+The solution below goes over most of the answers, and you are
+encouraged to use it when the hints aren’t enough - this is by
+design.
+
+
+Solution and walk-through
+
+(1) Basic browsing
+The most basic thing to look at is the history of commits.
+
+This is visible from a button in the repository view. We see every
+change, when, and who has committed.
+Every change has a unique identifier, such as 79ce3be
. This can
+be used to identify both this change, and the whole project’s
+version as of that change.
+Clicking on a change in the view shows more.
+
+
+
GitHub VS Code Command line
Click on the timeline symbol in the repository view:
+
+
+
+
+
This can be done from “Timeline”, in the bottom of explorer, but only
+for a single file.
+
+
+
+(2) Compare commit history with network graph
+The commit history we saw above looks linear: one commit after
+another. But if we look at the network view, we see some branching and merging points.
+We’ll see how to do these later. This is another one of the
+basic Git views.
+
+
GitHub VS Code Command line
In a new browser tab, open the “Insights” tab, and click on “Network”.
+You can hover over the commit dots to see the person who committed and
+how they correspond with the commits in the other view:
+
+
+
+
+
We don’t know how to do this without an extension. Try starting a terminal and using the
+“Command line” option.
+
This is a useful command to browse the network of commits locally:
+
$ git log --graph --oneline --decorate --all
+
+
+
To avoid having to type this long command every time, you can define an alias (shortcut):
+
$ git config --global alias.graph "log --graph --oneline --decorate --all"
+
+
+
From then on, you can use git graph
to see the network graph.
+
+
+
+(3) How can you browse the history of a single file?
+We see the history for the whole repository, but we can also see it
+for a single file.
+
+
GitHub VS Code Command line
Navigate to the file view: Main page → generate_predictions.py.
+Click the “History” button near the top right.
+
Open generate_predictions.py file in the editor. Under the file browser,
+we see a “Timeline” view there.
+
The git log
command can take a filename and provide the log of only
+a single file:
+
$ git log generate_predictions.py
+
+
+
+
+
+(4) Which files include the word “training”?
+Version control makes it very easy to find all occurrences of a
+word or pattern. This is useful for things like finding where functions or
+variables are defined or used.
+
+
GitHub VS Code Command line
We go to the main file view. We click the Search magnifying
+class at the very top, type “training”, and click enter. We see every
+instance, including the context.
+
+
Searching in a forked repository will not work instantaneously!
+
It usually takes a few minutes before one can search for keywords in a forked repository
+since it first needs to build the search index the very first time we search.
+Start it, continue with other steps, then come back to this.
+
+
If you use the “Search” magnifying class on the left sidebar, and
+search for “training” it shows the occurrences in every file. You can
+click to see the usage in context.
+
grep
is the command line tool that searches for lines matching a term
+
$ git grep training # only the lines
+$ git grep -C 3 training # three lines of context
+$ git grep -i training # case insensitive
+
+
+
+
+
+(5) Who modified a particular line last and when?
+This is called the “annotate” or “blame” view. The name “blame”
+is very unfortunate, but it is the standard term for historical reasons
+for this functionality and it is not meant to blame anyone.
+
+
GitHub VS Code Command line
From a file view, change preview to “Blame” towards the top-left.
+To get the actual commit, click on the commit message next
+to the code line that you are interested in.
+
This requires an extension. We recommend for now you use the command
+line version, after opening a terminal.
+
These two commands are similar but have slightly different output.
+
$ git annotate generate_predictions.py
+$ git blame generate_predictions.py
+
+
+
+
+
+(6) Can you use this code yourself? Are you allowed to share modifications?
+
+Look at the file LICENSE
.
+On GitHub, click on the file to see a nice summary of what we can do with this:
+
+
+
+
+
+
+
+
+Summary
+
+Git allowed us to understand this simple project much better than we
+could, if it was just a few files on our own computer.
+It was easy to share the project with the course.
+By forking the repository, we created our own copy. This is
+important for the following, where we will make changes to
+our copy.
+
+
+
+
+
+Creating branches and commits
+The first and most basic task to do in Git is record changes using
+commits. In this part, we will record changes in two
+ways: on a new branch (which supports multiple lines of work at once), and directly
+on the “main” branch (which happens to be the default branch here).
+
+
Objectives
+
+Record new changes to our own copy of the project.
+Understand adding changes in two separate branches.
+See how to compare different versions or branches.
+
+
+
+Background
+
+In the previous episode we have browsed an existing repository and saw commits
+and branches .
+Each commit is a snapshot of the entire project at a certain
+point in time and has a unique identifier (hash ) .
+A branch is a line of development, and the main
branch or master
branch
+are often the default branch in Git.
+A branch in Git is like a sticky note that is attached to a commit . When we add
+new commits to a branch, the sticky note moves to the new commit.
+Tags are a way to mark a specific commit as important, for example a release
+version. They are also like a sticky note, but they don’t move when new
+commits are added.
+
+
+
+
+
+What if two people, at the same time, make two different changes? Git
+can merge them together easily. Image created using https://gopherize.me/
+(inspiration ).
+
+
+
+
+Exercise: Creating branches and commits
+
+
+
+
+Illustration of what we want to achieve in this exercise.
+
+
+
+
Exercise: Practice creating commits and branches (20 min)
+
+First create a new branch and then either add a new file or modify an
+existing file and commit the change. Make sure that you now work on your
+copy of the example repository. In your new commit you can share a Git or
+programming trick you like or improve the documentation.
+In a new commit, modify the file again.
+Switch to the main
branch and create a commit there.
+Browse the network and locate the commits that you just created (“Insights” -> “Network”).
+Compare the branch that you created with the main
branch. Can you find an easy way to see the differences?
+Can you find a way to compare versions between two arbitrary commits in the repository?
+Try to rename the branch that you created and then browse the network again.
+Try to create a tag for one of the commits that you created (on GitHub,
+create a “release”).
+Optional: If this was too easy, you can try to create a new branch and on
+this branch work on one of these new features:
+
+The random seed is now set to a specific number (42). Make it possible to
+set the seed to another number or to turn off the seed setting via the command line interface.
+Move the code that does the majority vote to an own function.
+Write a test for the new majority vote function.
+The num_neighbors
in the code needs to be odd. Change the code so that it stops with an error message if an even number is given.
+Add type annotations to some functions.
+When calling the scatter_plot
function, call the function with named arguments.
+Add example usage to README.md.
+Add a Jupyter Notebook version of the example code.
+
+
+
+
+The solution below goes over most of the answers, and you are
+encouraged to use it when the hints aren’t enough - this is by
+design.
+
+
+Solution and walk-through
+
+(1) Create a new branch and a new commit
+
+
GitHub VS Code Command line
+Where it says “main” at the top left, click, enter a new branch
+name (e.g. new-tutorial
), then click on
+“Create branch … from main”.
+Make sure you are still on the new-tutorial
branch (it should say
+it at the top), and click “Add file” → “Create new file” from the
+upper right.
+Enter a filename where it says “Name your file…”.
+Share some Git or programming trick you like.
+Click “Commit changes”
+Enter a commit message. Then click “Commit
+changes”.
+
+
You should appear back at the file browser view, and see your
+modification there.
+
+Make sure that you are on the main branch.
+Version control button on left sidebar → Three dots in upper right of source control → Branch → Create branch.
+VS Code automatically switches to the new branch.
+Create a new file.
+In the version control sidebar, click the +
sign to add the file for the next commit.
+Enter a brief message and click “Commit”.
+
+
Create a new branch called new-tutorial
from main
and switch to it:
+
$ git switch --create new-tutorial main
+
+
+
Then create the new file. Finally add and commit the file:
+
$ git add tutorial.md # or a different file name
+$ git commit -m "sharing a programming trick"
+
+
+
+
+
+(2) Modify the file again with a new commit
+
+
GitHub VS Code Command line
This is similar to before, but we click on the existing file to
+modify.
+
+Click on the file you added or modified previously.
+Click the edit button, the pencil icon at top-right.
+Follow the “Commit changes” instructions as in the previous step.
+
+
Repeat as in the previous step.
+
Modify the file. Then commit the new change:
+
$ git add tutorial.md
+$ git commit -m "short summary of the change"
+
+
+
Make sure to replace “short summary of the change” with a meaningful commit
+message.
+
+
+
+(3) Switch to the main branch and create a commit there
+
+
GitHub VS Code Command line
+Go back to the main repository page (your user’s page).
+In the branch switch view (top left above the file view), switch to
+main
.
+Modify another file that already exists, following the pattern
+from above.
+
+
Use the branch selector at the bottom to switch back to the main branch. Repeat the same steps as above,
+but this time modify a different file.
+
First switch to the main
branch:
+
+
Then modify a file. Finally git add
and then commit the change:
+
$ git commit -m "short summary of the change"
+
+
+
+
+
+(4) Browse the commits you just made
+Let’s look at what we did. Now, the main
and the new branches
+have diverged: both have some modifications. Try to find the commits
+you created.
+
+
GitHub VS Code Command line
Insights tab → Network view (just like we have done before).
+
This requires an extension. Opening the VS Code terminal lets you use the
+command line method (View → Terminal will open a terminal at bottom). This is
+a normal command line interface and very useful for work.
+
$ git graph
+$ git log --graph --oneline --decorate --all # if you didn't define git graph yet.
+
+
+
+
+
+(5) Compare the branches
+Comparing changes is an important thing we need to do. When using the
+GitHub view only, this may not be so common, but we’ll show it so that
+it makes sense later on.
+
+
GitHub VS Code Command line
A nice way to compare branches is to add /compare
to the URL of the repository,
+for example (replace USER):
+https://github.com/USER /classification-task/compare
+
This seems to require an extension. We recommend you use the command line method.
+
$ git diff main new-tutorial
+
+
+
Try also the other way around:
+
$ git diff new-tutorial main
+
+
+
Try also this if you only want to see the file names that are different:
+
$ git diff --name-only main new-tutorial
+
+
+
+
+
+(6) Compare two arbitrary commits
+This is similar to above, but not only between branches.
+
+
GitHub VS Code Command line
Following the /compare
-trick above, one can compare commits on GitHub by
+adjusting the following URL:
+https://github.com/USER /classification-task/compare/VERSION1 ..VERSION2
+
Replace USER
with your username and VERSION1
and VERSION2
with a commit hash or branch name.
+Please try it out.
+
Again, we recommend using the Command Line method.
+
First try this to get a short overview of the commits:
+
+
Then try to compare any two commit identifiers with git diff
.
+
+
+
+(7) Renaming a branch
+
+
GitHub VS Code Command line
Branch button → View all branches → three dots at right side → Rename branch.
+
Version control sidebar → Three dots (same as in step 2) → Branch → Rename branch. Make sure you are on the right branch before you start.
+
Renaming the current branch:
+
$ git branch -m new-branch-name
+
+
+
Renaming a different branch:
+
$ git branch -m different-branch new-branch-name
+
+
+
+
+
+(8) Creating a tag
+Tags are a way to mark a specific commit as important, for example a release
+version. They are also like a sticky note, but they don’t move when new
+commits are added.
+
+
GitHub VS Code Command line
On the right side, below “Releases”, click on “Create a new release”.
+
What GitHub calls releases are actually tags in Git with additional metadata.
+For the purpose of this exercise we can use them interchangeably.
+
Version control sidebar → Three dots (same as in step 2) → Tags → Create tag. Make sure you are on the expected commit before you do this.
+
Creating a tag:
+
$ git tag -a v1.0 -m "New manuscript version for the pre-print"
+
+
+
+
+
+
+Summary
+In this part, we saw how we can make changes to our files. With branches, we
+can track several lines of work at once, and can compare their differences.
+
+You could commit directly to main
if there is only one single line
+of work and it’s only you.
+You could commit to branches if there are multiple lines of work at
+once, and you don’t want them to interfere with each other.
+Tags are useful to mark a specific commit as important, for example a
+release version.
+In Git, commits form a so-called “graph”. Branches are tags in Git function
+like sticky notes that stick to specific commits. What this means for us is
+that it does not cost any significant disk space to create new branches.
+Not all files should be added to Git. For example, temporary files or
+files with sensitive information or files which are generated as part of
+the build process should not be added to Git. For this we use
+.gitignore
(more about this later: Practical advice: How much Git is necessary? ).
+Unsure on which branch you are or what state the repository is in?
+On the command line, use git status
frequently to get a quick overview.
+
+
+
+
+Merging changes and contributing to the project
+Git allows us to have different development lines where we can try things out.
+It also allows different people to work on the same project at the same. This
+means that we have to somehow combine the changes later. In this part we will
+practice this: merging .
+
+
Objectives
+
+Understand that on GitHub merging is done through a pull request (on GitLab: “merge request”). Think
+of it as a change proposal .
+Create and merge a pull request within your own repository.
+Understand (and optionally) do the same across repositories, to contribute to
+the upstream public repository.
+
+
+
+Exercise
+
+
+
+
+Illustration of what we want to achieve in this exercise.
+
+
+
+
Exercise: Merging branches
+
+
GitHub Local (VS Code, Command line)
First, we make something called a pull request, which allows
+review and commenting before the actual merge.
+
We assume that in the previous exercise you have created a new branch with one
+or few new commits. We provide basic hints. You should refer to the solution
+as needed.
+
+Navigate to your branch from the previous episode
+(hint: the same branch view we used last time).
+Begin the pull request process
+(hint: There is a “Contribute” button in the branch view).
+Add or modify the pull request title and description, and verify the other data.
+In the pull request verify the target repository and the target
+branch. Make sure that you are merging within your own repository.
+GitHub: By default, it will offer to make the change to the
+upstream repository, workshop-material
. You should change this , you
+shouldn’t contribute your commit(s) upstream yet. Where it says
+base repository
, select your own repository.
+Create the pull request by clicking “Create pull request”. Browse
+the network view to see if anything has changed yet.
+Merge the pull request, or if you are not on GitHub you can merge
+the branch locally. Browse the network again. What has changed?
+Find out which branches are merged and thus safe to delete. Then remove them
+and verify that the commits are still there, only the branch labels are
+gone (hint: you can delete branches that have been merged into main
).
+Optional: Try to create a new branch with a new change, then open a pull
+request but towards the original (upstream) repository. We will later merge few of
+those.
+
+
When working locally, it’s easier to merge branches: we can just do
+the merge, without making a pull request. But we don’t have that step
+of review and commenting and possibly adjusting.
+
+Switch to the main
branch that you want to merge the other
+branch into. (Note that this is the other way around from the
+GitHub path).
+
+
Then:
+
+Merge the other branch into main
(which is then your current branch).
+Find out which branches are merged and thus safe to delete. Then remove them
+and verify that the commits are still there, only the branch labels are
+gone. (Hint: you can delete branches that have been merged into main
).
+Optional: Try to create a new branch, and make a
+GitHub pull request with your change, and contribute it to our
+upstream repository.
+
+
+
+The solution below goes over most of the answers, and you are
+encouraged to use it when the hints aren’t enough - this is by design.
+
+
+Solution and walk-through
+
+(1) Navigate to your branch
+Before making the pull request, or doing a merge, it’s important to
+make sure that you are on the right branch. Many people have been
+frustrated because they forgot this!
+
+
GitHub VS Code Command line
GitHub will notice a recently changed branch and offer to make a pull request (clicking there will bring you to step 3):
+
+
+
+
+
If the yellow box is not there, make sure you are on the branch you want to
+merge from :
+
+
+
+
+
Remember, you need to switch to the main
branch, the branch we want to merge to .
+This is different from the GitHub path.
+
Remember, you need to switch to the main
branch, the branch we want to merge to .
+This is different from the GitHub path:
+
+
+
+
+(2) Begin the pull request process
+In GitHub, the pull request is the way we propose to merge two
+branches together. We start the process of making one.
+
+
GitHub VS Code Command line
+
+
+
+
It is possible to open pull requests from the editor, but
+we don’t cover that here.
+
If you are working locally, continue to step 5.
+
It is possible to open pull requests from the command line, but
+we don’t cover that here.
+
If you are working locally, continue to step 5.
+
+
+
+(3) Fill out and verify the pull request
+Check that the pull request is directed to the right repository and branch
+and that it contains the changes that you meant to merge.
+
+
GitHub VS Code Command line
Things to check:
+
+Base repository: this should be your own
+Title: make it descriptive
+Description: make it informative
+Scroll down to see commits: are these the ones you want to merge?
+Scroll down to see the changes: are these the ones you want to merge?
+
+
+
+
+This screenshot only shows the top part. If you scroll down, you
+can see the commits and the changes. We recommend to do this before
+clicking on “Create pull request”.
+
+
+
+
+
If you are working locally, continue to step 5.
+
If you are working locally, continue to step 5.
+
+
+
+(4) Create the pull request
+We actually create the pull request. Don’t forget to navigate to the Network
+view after opening the pull request. Note that the changes proposed in the
+pull request are not yet merged.
+
+
GitHub VS Code Command line
Click on the green button “Create pull request”.
+
If you click on the little arrow next to “Create pull request”, you can also
+see the option to “Create draft pull request”. This will be interesting later
+when collaborating with others. It allows you to open a pull request that is
+not ready to be merged yet, but you want to show it to others and get feedback.
+
It is possible to create pull requests from the editor, but
+we don’t cover that here.
+
If you are working locally, continue to step 5.
+
It is possible to create pull requests from the command line, but
+we don’t cover that here.
+
If you are working locally, continue to step 5.
+
+
+
+(5) Merge the pull request
+Now, we do the actual merging. We see some effects now.
+
+
GitHub VS Code Command line
Review it again (commits and changes), and then click “Merge pull request”.
+
After merging, verify the network view. Also navigate then to your “main”
+branch and check that your change is there.
+
Just like with the command line, when we merge we modify our current branch. Verify you are on the main
branch.
+
+Verify current branch at the bottom.
+From the version control sidebar → Three dots → Branch → Merge.
+In the selector that comes up, choose the branch you want to merge from .
+The commits on that branch will be added to the current branch.
+
+
On the command line, when we merge, we always modify our current branch.
+
If you are not sure anymore what your current branch is, type:
+
+
… or equally useful to see where we are right now:
+
+
In this case we merge the new-tutorial
branch into our current branch:
+
$ git merge new-tutorial
+
+
+
+
+
+(6) Delete merged branches
+Before deleting branches, first check whether they are merged.
+If you delete an un-merged branch, it will be difficult to find the commits
+that were on that branch. If you delete a merged branch, the commits are now
+also part of the branch where we have merged to.
+
+
GitHub VS Code Command line
One way to delete the branch is to click on the “Delete branch” button after the pull
+request is merged:
+
+
+
+
+
But what if we forgot? Then navigate to the branch view:
+
+
+
+
+
In the overview we can see that it has been merged and we can delete it.
+
From the Source Control sidebar → the three dots (as before) → Branch → Delete Branch. Select the branch name to delete.
+
Verify which branches are merged to the current branch:
+
$ git branch --merged
+
+* main
+ new-tutorial
+
+
+
This means that it is safe to delete the new-tutorial
branch:
+
$ git branch -d new-tutorial
+
+
+
Verify then that the branch is gone but that the commits are still there:
+
$ git branch
+$ git log --oneline
+
+
+
+
+
+(7) Contribute to the original repository with a pull request
+This is an advanced step. We will practice this tomorrow and
+it is OK to skip this at this stage.
+
+
GitHub VS Code Command line
Now that you know how to create branches and opening a pull request, try to
+open a new pull request with a new change but this time the base repository
+should be the upstream one.
+
In other words, you now send a pull request across repositories : from your fork
+to the original repository.
+
Another thing that is different now is that you might not have permissions to
+merge the pull request. We can then together review and browse the pull
+request.
+
Not described. We will return to this when we discuss collaboration using GitHub.
+
You would create a new branch locally like above, push it to GitHub to your own
+user’s repository, and from there open a pull request towards the upstream
+repository.
+
Not described. We will return to this when we discuss collaboration using GitHub.
+
You would create a new branch locally like above, push it to GitHub to your own
+user’s repository, and from there open a pull request towards the upstream
+repository.
+
+
+
+
+Summary
+
+We learned how to merge two branches together.
+When is this useful? This is not only useful to combine development lines in
+your own work. Being able to merge branches also forms the basis for collaboration .
+Branches which are merged to other branches are safe to delete, since we only
+delete the “sticky note” next to a commit, not the commits themselves.
+
+
+
+
+Conflict resolution
+
+Resolving a conflict (demonstration)
+A conflict is when Git asks humans to decide during a merge which of two
+changes to keep if the same portion of a file has been changed in two
+different ways on two different branches .
+We will practice conflict resolution in the collaborative Git lesson (next
+day).
+Here we will only demonstrate how to create a conflict and how to resolve it,
+all on GitHub . Once we understand how this works, we will be more confident
+to resolve conflicts also in the command line (we can demonstrate this if
+we have time).
+How to create a conflict (please try this in your own time and just watch now ):
+
+Create a new branch from main
and on it make a change to a file.
+On main
, make a different change to the same part of the same file.
+Now try to merge the new branch to main
. You will get a conflict.
+
+How to resolve conflicts:
+
+On GitHub, you can resolve conflicts by clicking on the “Resolve conflicts”
+button. This will open a text editor where you can choose which changes to
+keep.
+Make sure to remove the conflict markers.
+After resolving the conflict, you can commit the changes and merge the
+pull request.
+Sometimes a conflict is between your change and somebody else’s change. In
+that case, you might have to discuss with the other person which changes to
+keep.
+
+
+
+Avoiding conflicts
+
+
The human side of conflicts
+
+What does it mean if two people do the same thing in two different ways?
+What if you work on the same file but do two different things in the different sections?
+What if you do something, don’t tell someone from 6 months, and then try to combine it with other people’s work?
+How are conflicts avoided in other work? (Only one person working at once?
+Declaring what you are doing before you start, if there is any chance someone
+else might do the same thing, helps.)
+
+
+
+Human measures
+
+
+Collaboration measures
+
+
+Project layout measures
+
+
+Technical measures
+
+Share your changes early and often : This is one of the happy,
+rare circumstances when everyone doing the selfish thing (publishing your
+changes as early as practical) results in best case for everyone!
+Pull/rebase often to keep up to date with upstream.
+Resolve conflicts early.
+
+
+
+
+
+
+Practical advice: How much Git is necessary?
+
+Writing useful commit messages
+Useful commit messages summarize the change and provide context .
+If you need a commit message that is longer than one line,
+then the convention is: one line summarizing the commit, then one empty line,
+then paragraph(s) with more details in free form, if necessary.
+Good example:
+increase alpha to 2.0 for faster convergence
+
+the motivation for this change is
+to enable ...
+...
+(more context)
+...
+this is based on a discussion in #123
+
+
+
+Why something was changed is more important than what has changed.
+Cross-reference to issues and discussions if possible/relevant.
+Bad commit messages: “fix”, “oops”, “save work”
+Just for fun, a page collecting bad examples: http://whatthecommit.com
+Write commit messages that will be understood
+15 years from now by someone else than you. Or by your future you.
+Many projects start out as projects “just for me” and end up to be successful projects
+that are developed by 50 people over decades.
+Commits with multiple authors are possible.
+
+Good references:
+
+
+
Note
+
A great way to learn how to write commit messages and to get inspired by their
+style choices: browse repositories of codes that you use/like :
+
Some examples (but there are so many good examples):
+
+
When designing commit message styles consider also these:
+
+How will you easily generate a changelog or release notes?
+During code review, you can help each other improving commit messages.
+
+
+But remember: it is better to make any commit, than no commit. Especially in small projects.
+Let not the perfect be the enemy of the good enough .
+
+
+What level of branching complexity is necessary for each project?
+Simple personal projects :
+
+Typically start with just the main
branch.
+Use branches for unfinished/untested ideas.
+Use branches when you are not sure about a change.
+Use tags to mark important milestones.
+If you are unsure what to do with unfinished and not working code, commit it
+to a branch.
+
+Projects with few persons: you accept things breaking sometimes
+
+Projects with few persons: changes are reviewed by others
+
+You create new feature branches for changes.
+Changes are reviewed before they are merged to the main
branch.
+Consider to write-protect the main
branch so that it can only be changed
+with pull requests or merge requests.
+
+
+
+How large should a commit be?
+
+Better too small than too large (easier to combine than to split).
+Often I make a commit at the end of the day (this is a unit I would not like to lose).
+Smaller sized commits may be easier to review for others than huge commits.
+A commit should not contain unrelated changes to simplify review and possible
+repair/adjustments/undo later (but again: imperfect commits are better than no commits).
+Imperfect commits are better than no commits.
+
+
+
+Working on the command line? Use “git status” all the time
+The git status
command is one of the most useful commands in Git
+to inform about which branch we are on, what we are about to commit,
+which files might not be tracked, etc.
+
+
+How about staging and committing?
+
+Commit early and often: rather create too many commits than too few.
+You can always combine commits later.
+Once you commit, it is very, very hard to really lose your code.
+Always fully commit (or stash) before you do dangerous things, so that you know you are safe.
+Otherwise it can be hard to recover.
+Later you can start using the staging area (where you first stage and then commit in a second step).
+Later start using git add -p
and/or git commit -p
.
+
+
+
+What to avoid
+
+Committing generated files/directories (example: __pycache__
, *.pyc
) ->
+use .gitignore
+files
+(collection of .gitignore templates ).
+Committing huge files -> use code review to detect this.
+Committing unrelated changes together.
+Postponing commits because the changes are “unfinished”/”ugly” -> better ugly
+commits than no commits.
+When working with branches:
+
+Working on unrelated things on the same branch.
+Not updating your branch before starting new work.
+Too ambitious branch which risks to never get completed.
+Over-engineering the branch layout and safeguards in small projects -> can turn people away from your project.
+
+
+
+
+
+
+Optional: How to turn your project to a Git repo and share it
+
+
Objectives
+
+Turn our own coding project (small or large, finished or unfinished) into a
+Git repository.
+Be able to share a repository on the web to have a backup or so that others
+can reuse and collaborate or even just find it.
+
+
+
+Exercise
+
+
+
+
+From a bunch of files to a local repository which we then share on GitHub.
+
+
+
+
Exercise: Turn your project to a Git repo and share it (20 min)
+
+Create a new directory called myproject (or a different name) with one or few files in it.
+This represents our own project. It is not yet a Git repository. You can try
+that with your own project or use a simple placeholder example.
+Turn this new directory into a Git repository.
+Share this repository on GitHub (or GitLab, since it really works the same).
+
+
We offer three different paths of how to do this exercise.
+
+Via GitHub web interface : easy and can be a good starting point if you are completely
+new to Git.
+VS Code is quite easy, since VS Code can offer to create the
+GitHub repositories for you.
+Command line : you need to create the
+repository on GitHub and link it yourself.
+
+
+
Only using GitHub VS Code Command line RStudio
Create an repository on GitHub
+
First log into GitHub, then follow the screenshots and descriptions below.
+
+
+
+
+Click on the “plus” symbol on top right, then on “New repository”.
+
+
+
Then:
+
+
+
+
+Choose a repository name, add a short description, and in this case make sure to check “Add a
+README file”. Finally “Create repository”.
+
+
+
Upload your files
+
Now that the repository is created, you can upload your files:
+
+
+
+
+Click on the “+” symbol and then on “Upload files”.
+
+
+
In VS Code it only takes few clicks.
+
First, open the folder in VS Code. My example project here contains two files.
+Then click on the source control icon:
+
+
+
+
+Open the folder in VS Code. Then click on the source control icon.
+
+
+
+
+
+
+Choose “Publish to GitHub”. In this case I never even clicked on “Initialize Repository”.
+
+
+
+
+
+
+In my case I chose to “Publish to GitHub public repository”. Here you can also rename
+the repository if needed.
+
+
+
+
+
+
+First time you do this you might need to authorize VS Code to access your
+GitHub account by redirecting you to https://github.com/login/oauth/authorize.
+
+
+
+
+
+
+After it is published, click on “Open on GitHub”.
+
+
+
Put your project under version control
+
My example project here consists of two files. Replace this with your own
+example files:
+
$ ls -l
+
+.rw------- 19k user 7 Mar 17:36 LICENSE
+.rw------- 21 user 7 Mar 17:36 myscript.py
+
+
+
I will first initialize a Git repository in this directory.
+If you get an error, try without the -b main
(and your default branch will
+then be called master
, this will happen for Git versions older than
+2.28):
+
+
Now add and commit the two files to the Git repository:
+
$ git add LICENSE myscript.py
+$ git commit -m "putting my project under version control"
+
+
+
If you want to add all files in one go, you can use git add .
instead of git add LICENSE myscript.py
.
+
Now you have a Git repository with one commit. Verify this with git log
.
+But it’s still only on your computer. Let’s put it on GitHub next.
+
Create an empty repository on GitHub
+
First log into GitHub, then follow the screenshots and descriptions below.
+
+
+
+
+Click on the “plus” symbol on top right, then on “New repository”.
+
+
+
Then create an empty repository without any files and without any commits:
+
+
+
+
+Choose a repository name, add a short description, but please do not check “Add a
+README file”. For “Add .gitignore” and “Choose a license” also leave as “None”. Finally “Create repository”.
+
+
+
Once you click the green “Create repository”, you will see a page similar to:
+
+
+
+
+
What this means is that we have now an empty project with either an HTTPS or an
+SSH address: click on the HTTPS and SSH buttons to see what happens.
+
Push an existing repository from your computer to GitHub
+
We now want to follow the “… or push an existing repository from the command line ”:
+
+In your terminal make sure you are still in your myproject directory.
+Copy paste the three lines below the red arrow to the terminal and execute
+those, in my case (you need to replace the “USER” part and possibly also
+the repository name ):
+
+
+
SSH HTTPS
$ git remote add origin git@github.com:USER/myproject.git
+
+
+
$ git remote add origin https://github.com/USER/myproject.git
+
+
+
+
Then:
+
$ git branch -M main
+$ git push -u origin main
+
+
+
The meaning of the above lines:
+
+Add a remote reference with the name “origin”
+Rename current branch to “main”
+Push branch “main” to “origin”
+
+
You should now see:
+
Enumerating objects: 4, done.
+Counting objects: 100% (4/4), done.
+Delta compression using up to 12 threads
+Compressing objects: 100% (3/3), done.
+Writing objects: 100% (4/4), 6.08 KiB | 6.08 MiB/s, done.
+Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
+To github.com:USER/myproject.git
+ * [new branch] main -> main
+branch 'main' set up to track 'origin/main'.
+
+
+
Reload your GitHub project website and your commits should now be
+online!
+
Troubleshooting
+
error: remote origin already exists
+
+
remote contains work that you do not have
+
+
This is not fully explained, because a lot of it is similar to the “Command
+line” method (and an RStudio expert could help us some). The main differences
+are:
+
Put your project under version control
+
+Tools → Version control → Project setup → Version control system = Git.
+Select “Yes” for “do you want to initialize a new git repository for this project.
+Select yes to restart the project with RStudio.
+Switch to branch main
to have you branch named that.
+
+
Create an empty repository on GitHub
+
Same as command line
+
Push an existing repository from your computer to GitHub
+
+Under the “Create new branch” button → “Add Remote”
+Remote name: origin
+Remote URL: as in command line (remember to select SSH or HTTPS as you have configured your RStudio)
+The “Push” (up arrow) button will send changes on your current branch to the remote. The “Pull” (down arrow) will get changes from the remote.
+
+
Troubleshooting
+
Same as command line
+
+
+
+
+Is putting software on GitHub/GitLab/… publishing?
+It is a good first step but to make your code truly findable and
+accessible , consider making your code citable and persistent : Get a
+persistent identifier (PID) such as DOI in addition to sharing the code
+publicly, by using services like Zenodo or similar
+services.
+More about this in How to publish your code .
+
+
+
+
+
+Where to start with documentation
+
+
Objectives
+
+Discuss what makes good documentation.
+Improve the README of your project or our example project.
+Explore Sphinx which is a popular tool to build documentation websites.
+Learn how to leverage GitHub Actions and GitHub Pages to build and deploy documentation.
+
+
+
+
+Why? 💗✉️ to your future self
+
+
+
+In-code documentation
+Not very useful (more commentary than comment):
+# now we check if temperature is below -50
+if temperature < - 50 :
+ print ( "ERROR: temperature is too low" )
+
+
+More useful (explaining why ):
+# we regard temperatures below -50 degrees as measurement errors
+if temperature < - 50 :
+ print ( "ERROR: temperature is too low" )
+
+
+Keeping zombie code “just in case” (rather use version control):
+# do not run this code!
+# if temperature > 0:
+# print("It is warm")
+
+
+Emulating version control:
+# John Doe: threshold changed from 0 to 15 on August 5, 2013
+if temperature > 15 :
+ print ( "It is warm" )
+
+
+
+
+Many languages allow “docstrings”
+Example (Python):
+def kelvin_to_celsius ( temp_k : float ) -> float :
+ """
+ Converts temperature in Kelvin to Celsius.
+
+ Parameters
+ ----------
+ temp_k : float
+ temperature in Kelvin
+
+ Returns
+ -------
+ temp_c : float
+ temperature in Celsius
+ """
+ assert temp_k >= 0.0 , "ERROR: negative T_K"
+
+ temp_c = temp_k - 273.15
+
+ return temp_c
+
+
+
+
Keypoints
+
+Documentation which is only in the source code is not enough.
+Often a README is enough.
+Documentation needs to be kept
+in the same Git repository as the code since we want it to evolve with
+the code.
+
+
+
+
+Often a README is enough - checklist
+
+Purpose
+Requirements
+Installation instructions
+Copy-paste-able example to get started
+Tutorials covering key functionality
+Reference documentation (e.g. API) covering all functionality
+Authors and recommended citation
+License
+Contribution guide
+
+See also the
+JOSS review checklist .
+
+
+Diátaxis
+Diátaxis is a systematic approach to technical documentation authoring.
+
+
+
+What if you need more than a README?
+
+
+
+Exercise: Set up a Sphinx documentation
+
+
Preparation
+
In this episode we will use the following 5 packages which we installed
+previously as part of the Software install instructions :
+
myst - parser
+sphinx
+sphinx - rtd - theme
+sphinx - autoapi
+sphinx - autobuild
+
+
+
Which repository to use? You have 3 options:
+
+Clone your fork of the example repository.
+If you don’t have that, you can clone the exercise repository itself.
+You can try this with your own project and the project does not have to
+be a Python project.
+
+
+There are at least two ways to get started with Sphinx:
+
+Use sphinx-quickstart
to create a new Sphinx project.
+This is what we will do instead : Create three files (doc/conf.py
, doc/index.md
, and doc/about.md
)
+as starting point and improve from there.
+
+
+
Exercise: Set up a Sphinx documentation
+
+Create the following three files in your project:
+ your-project/
+├── doc/
+│ ├── conf.py
+│ ├── index.md
+│ └── about.md
+└── ...
+
+
+This is conf.py
:
+project = "your-project"
+copyright = "2025, Authors"
+author = "Authors"
+release = "0.1"
+
+exclude_patterns = [ "_build" , "Thumbs.db" , ".DS_Store" ]
+
+extensions = [
+ "myst_parser" , # in order to use markdown
+]
+
+myst_enable_extensions = [
+ "colon_fence" , # ::: can be used instead of ``` for better rendering
+]
+
+html_theme = "sphinx_rtd_theme"
+
+
+This is index.md
(feel free to change the example text):
+# Our code documentation
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
+incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
+nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
+culpa qui officia deserunt mollit anim id est laborum.
+
+:::{toctree}
+:maxdepth: 2
+:caption: Some caption
+
+about.md
+:::
+
+
+This is about.md
(feel free to adjust):
+# About this code
+
+Work in progress ...
+
+
+
+Run sphinx-build
to build the HTML documentation:
+$ sphinx-build doc _build
+
+... lots of output ...
+The HTML pages are in _build.
+
+
+
+Try to open _build/index.html
in your browser.
+Experiment with adding more content, images, equations, code blocks, …
+
+
+
+
+There is a lot more you can do:
+
+This is useful if you want to check the integrity of all internal and external links:
+$ sphinx-build doc -W -b linkcheck _build
+
+
+
+sphinx-autobuild
+provides a local web server that will automatically refresh your view
+every time you save a file - which makes writing with live-preview much easier.
+
+
+
+Demo: Building documentation with GitHub Actions
+
+First we need to extend the environment.yml
file to include the necessary packages:
+name : classification-task
+channels :
+ - conda-forge
+dependencies :
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - myst-parser
+ - sphinx
+ - sphinx-rtd-theme
+ - sphinx-autoapi
+
+
+Then we add a GitHub Actions workflow .github/workflow/sphinx.yml
to build the documentation:
+name : Build documentation
+
+on :
+ push :
+ branches : [ main ]
+ pull_request :
+ branches : [ main ]
+
+permissions :
+ contents : write
+
+jobs :
+ docs :
+ runs-on : ubuntu-24.04
+
+ steps :
+ - name : Checkout
+ uses : actions/checkout@v4
+
+ - uses : mamba-org/setup-micromamba@v1
+ with :
+ micromamba-version : '2.0.5-0' # any version from https://github.com/mamba-org/micromamba-releases
+ environment-file : environment.yml
+ init-shell : bash
+ cache-environment : true
+ post-cleanup : 'all'
+ generate-run-shell : false
+
+ - name : Sphinx build
+ run : |
+ sphinx-build doc _build
+ shell : bash -el {0}
+
+ - name : Deploy to GitHub Pages
+ uses : peaceiris/actions-gh-pages@v4
+ if : ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
+ with :
+ publish_branch : gh-pages
+ github_token : ${{ secrets.GITHUB_TOKEN }}
+ publish_dir : _build/
+ force_orphan : true
+
+
+Now:
+
+Add these two changes to the GitHub repository.
+Go to “Settings” -> “Pages” -> “Branch” -> gh-pages
-> “Save”.
+Look at “Actions” tab and observe the workflow running and hopefully
+deploying the website.
+Finally visit the generated site. You can find it by clicking the About wheel
+icon on top right of your repository. There, select “Use your GitHub Pages
+website”.
+This is how we build almost all of our lesson websites ,
+including this one!
+Another popular place to deploy Sphinx documentation is ReadTheDocs .
+
+
+
+Optional: How to auto-generate API documentation in Python
+Add three tiny modifications (highlighted) to doc/conf.py
to auto-generate API documentation
+(this requires the sphinx-autoapi
package):
+project = "your-project"
+copyright = "2025, Authors"
+author = "Authors"
+release = "0.1"
+
+exclude_patterns = [ "_build" , "Thumbs.db" , ".DS_Store" ]
+
+extensions = [
+ "myst_parser" , # in order to use markdown
+ "autoapi.extension" , # in order to use markdown
+ ]
+
+# search this directory for Python files
+autoapi_dirs = [ ".." ]
+
+# ignore this file when generating API documentation
+autoapi_ignore = [ "*/conf.py" ]
+
+myst_enable_extensions = [
+ "colon_fence" , # ::: can be used instead of ``` for better rendering
+]
+
+html_theme = "sphinx_rtd_theme"
+
+
+Then rebuild the documentation (or push the changes and let GitHub rebuild it)
+and you should see a new section “API Reference”.
+
+
+Possibilities to host Sphinx documentation
+
+
+
+Confused about reStructuredText vs. Markdown vs. MyST?
+
+At the beginning there was reStructuredText and Sphinx was built for reStructuredText.
+Independently, Markdown was invented and evolved into a couple of flavors.
+Markdown became more and more popular but was limited compared to reStructuredText.
+Later, MyST
+was invented to be able to write
+something that looks like Markdown but in addition can do everything that
+reStructuredText can do with extra directives.
+
+
+
+
+
+Collaborative version control and code review
+
+
+Concepts around collaboration
+
+
+Commits, branches, repositories, forks, clones
+
+repository : The project, contains all data and history (commits, branches, tags).
+commit : Snapshot of the project, gets a unique identifier (e.g. c7f0e8bfc718be04525847fc7ac237f470add76e
).
+branch : Independent development line. The main development line is often called main
.
+tag : A pointer to one commit, to be able to refer to it later. Like a commemorative plaque
+that you attach to a particular commit (e.g. phd-printed
or paper-submitted
).
+cloning : Copying the whole repository to your laptop - the first time. It is not necessary to download each file one by one.
+forking : Taking a copy of a repository (which is typically not yours) - your
+copy (fork) stays on GitHub/GitLab and you can make changes to your copy.
+
+
+
+Cloning a repository
+In order to make a complete copy a whole repository, the git clone
command
+can be used. When cloning, all the files, of all or selected branches, of a
+repository are copied in one operation. Cloning of a repository is of relevance
+in a few different situations:
+
+Working on your own, cloning is the operation that you can use to create
+multiple instances of a repository on, for instance, a personal computer, a
+server, and a supercomputer.
+The parent repository could be a repository that you or your colleague own. A
+common use case for cloning is when working together within a smaller team
+where everyone has read and write access to the same git repository.
+Alternatively, cloning can be made from a public repository of a code that
+you would like to use. Perhaps you have no intention to work on the code, but
+would like to stay in tune with the latest developments, also in-between
+releases of new versions of the code.
+
+
+
+
+
+Forking and cloning
+
+
+
+
+Forking a repository
+When a fork is made on GitHub/GitLab a complete copy, of all or selected
+branches, of the repository is made. The copy will reside under a different
+account on GitHub/GitLab. Forking of a repository is of high relevance when
+working with a git repository to which you do not have write access.
+
+In the fork repository commits can be made to the base branch (main
or
+master
), and to other branches.
+The commits that are made within the branches of the fork repository can be
+contributed back to the parent repository by means of pull or merge requests.
+
+
+
+Synchronizing changes between repositories
+
+We need a mechanism to communicate changes between the repositories.
+We will pull or fetch updates from remote repositories (we will soon discuss the difference between pull and fetch).
+We will push updates to remote repositories.
+We will learn how to suggest changes within repositories on GitHub and across repositories (pull request ).
+Repositories that are forked or cloned do not automatically synchronize themselves:
+We will learn how to update forks (by pulling from the “central” repository).
+A main difference between cloning a repository and forking a repository is that the former is a general operation for generating copies of a repository to different computers, whereas forking is a particular operation implemented on GitHub/GitLab.
+
+
+
+
+Collaborating within the same repository
+In this episode, we will learn how to collaborate within the same repository.
+We will learn how to cross-reference issues and pull requests, how to review
+pull requests, and how to use draft pull requests.
+This exercise will form a good basis for collaboration that is suitable for
+most research groups.
+
+
Note
+
When you read or hear pull request , please think of a change proposal .
+
+
+Exercise
+In this exercise, we will contribute to a repository via a pull request .
+This means that you propose some change, and then it is accepted (or not).
+
+
Exercise preparation
+
+First we need to get access to the exercise repository to which we will
+contribute.
+
+
+Don’t forget to accept the invitation
+
+Check https://github.com/settings/organizations/
+Alternatively check the inbox for the email account you registered with
+GitHub. GitHub emails you an invitation link, but if you don’t receive it
+you can go to your GitHub notifications in the top right corner. The
+maintainer can also “copy invite link” and share it within the group.
+
+
+Watching and unwatching repositories
+
+Now that you are a collaborator, you get notified about new issues and pull
+requests via email.
+If you do not wish this, you can “unwatch” a repository (top of
+the project page).
+However, we recommend watching repositories you are interested
+in. You can learn things from experts just by watching the
+activity that come through a popular project.
+
+
+
+
+Unwatch a repository by clicking “Unwatch” in the repository view,
+then “Participating and @mentions” - this way, you will get
+notifications about your own interactions.
+
+
+
+
+
+
+
Exercise: Collaborating within the same repository (25 min)
+
Technical requirements (from installation instructions):
+
+
What is familiar from the previous workshop day (not repeated here):
+
+
What will be new in this exercise:
+
+If you create the changes locally, you will need to push them to the remote repository.
+Learning what a protected branch is and how to modify a protected branch: using a pull request.
+Cross-referencing issues and pull requests.
+Practice to review a pull request.
+Learn about the value of draft pull requests.
+
+
Exercise tasks :
+
+Start in the exercise
+repository and open an
+issue where you describe the change you want to make. Note down the issue
+number since you will need it later.
+Create a new branch.
+Make a change to the recipe book on the new branch and in the commit
+cross-reference the issue you opened (see the walk-through below
+for how to do that).
+Push your new branch (with the new commit) to the repository you
+are working on.
+Open a pull request towards the main branch.
+Review somebody else’s pull request and give constructive feedback. Merge their pull request.
+Try to create a new branch with some half-finished work and open a draft
+pull request. Verify that the draft pull request cannot be merged since it
+is not meant to be merged yet.
+
+
+
+
+Solution and hints
+
+(1) Opening an issue
+This is done through the GitHub web interface. For example, you could
+give the name of the recipe you want to add (so that others don’t add
+the same one). It is the “Issues” tab.
+
+
+(2) Create a new branch.
+If on GitHub, you can make the branch in the web interface.
+
+
+(3) Make a change adding the recipe
+Add a new file with the recipe in it. Commit the file. In the commit
+message, include the note about the issue number, saying that this
+will close that issue.
+
+Cross-referencing issues and pull requests
+Each issue and each pull request gets a number and you can cross-reference them.
+When you open an issue, note down the issue number (in this case it is #2
):
+
+
+
+
+You can reference this issue number in a commit message or in a pull request, like in this
+commit message:
+ this is the new recipe; fixes #2
+
+
+If you forget to do that in your commit message, you can also reference the issue
+in the pull request description. And instead of fixes
you can also use closes
or resolves
+or fix
or close
or resolve
(case insensitive).
+Here are all the keywords that GitHub recognizes:
+https://help.github.com/en/articles/closing-issues-using-keywords
+Then observe what happens in the issue once your commit gets merged: it will
+automatically close the issue and create a link between the issue and the
+commit. This is very useful for tracking what changes were made in response to
+which issue and to know from when until when precisely the issue was open.
+
+
+
+(4) Push to GitHub as a new branch
+Push the branch to the repository. You should end up with a branch
+visible in the GitHub web view.
+This is only necessary if you created the changes locally. If you created the
+changes directly on GitHub, you can skip this step.
+
+
VS Code Command line
In VS Code, you can “publish the branch” to the remote repository by clicking
+the cloud icon in the bottom left corner of the window:
+
+
+
+
+
If you have a local branch my-branch
and you want to push it to the remote
+and make it visible there, first verify what your remote is:
+
$ git remote --verbose
+
+origin git@github.com:USER/recipe-book.git (fetch)
+origin git@github.com:USER/recipe-book.git (push)
+
+
+
In this case the remote is called origin
and refers to the address
+git@github.com:USER/recipe-book.git. Both can be used
+interchangeably. Make sure it points to the right repository, ideally a
+repository that you can write to.
+
Now that you have a remote, you can push your branch to it:
+
$ git push origin my-branch
+
+
+
This will create a new branch on the remote repository with the same name as
+your local branch.
+
You can also do this:
+
$ git push --set-upstream origin my-branch
+
+
+
This will connect the local branch and the remote branch so that in the future
+you can just type git push
and git pull
without specifying the branch name
+(but this depends on your Git configuration).
+
Troubleshooting
+
If you don’t have a remote yet, you can add it with (adjust ADDRESS
to your repository address):
+
$ git remote add origin ADDRESS
+
+
+
ADDRESS is the part that you copy from here:
+
+
+
+
+
If the remote points to the wrong place, you can change it with:
+
$ git remote set-url origin NEWADDRESS
+
+
+
+
+
+(5) Open a pull request towards the main branch
+This is done through the GitHub web interface.
+
+
+(6) Reviewing pull requests
+You review through the GitHub web interface.
+Checklist for reviewing a pull request:
+
+Be kind, on the other side is a human who has put effort into this.
+Be constructive: if you see a problem, suggest a solution.
+Towards which branch is this directed?
+Is the title descriptive?
+Is the description informative?
+Scroll down to see commits.
+Scroll down to see the changes.
+If you get incredibly many changes, also consider the license or copyright
+and ask where all that code is coming from.
+Again, be kind and constructive.
+Later we will learn how to suggest changes directly in the pull request.
+
+If someone is new, it’s often nice to say something encouraging in the
+comments before merging (even if it’s just “thanks”). If all is good
+and there’s not much else to say, you could merge directly.
+
+
+(7) Draft pull requests
+Try to create a draft pull request:
+
+
+
+
+Verify that the draft pull request cannot be merged until it is marked as ready
+for review:
+
+
+
+
+Draft pull requests can be useful for:
+
+Feedback : You can open a pull request early to get feedback on your work without
+signaling that it is ready to merge.
+Information : They can help communicating to others that a change is coming up and in
+progress.
+
+
+
+What is a protected branch? And how to modify it?
+A protected branch on GitHub or GitLab is a branch that cannot (accidentally)
+deleted or force-pushed to. It is also possible to require that a branch cannot
+be directly pushed to or modified, but that changes must be submitted via a
+pull request.
+To protect a branch in your own repository, go to “Settings” -> “Branches”.
+
+
+Summary
+
+We used all the same pieces that we’ve learned previously.
+But we successfully contributed to a collaborative project !
+The pull request allowed us to contribute without changing directly:
+this is very good when it’s not mainly our project.
+
+
+
+
+
+Practicing code review
+In this episode we will practice the code review process. We will learn how to
+ask for changes in a pull request, how to suggest a change in a pull request,
+and how to modify a pull request.
+This will enable research groups to work more collaboratively and to not only
+improve the code quality but also to learn from each other .
+
+Exercise
+
+
Exercise preparation
+
We can continue in the same exercise repository which we have used in the
+previous episode.
+
+
+
Exercise: Practicing code review (25 min)
+
Technical requirements :
+
+
What is familiar from previous lessons:
+
+
What will be new in this exercise:
+
+As a reviewer, we will learn how to ask for changes in a pull request.
+As a reviewer, we will learn how to suggest a change in a pull request.
+As a submitter, we will learn how to modify a pull request without closing
+the incomplete one and opening a new one.
+
+
Exercise tasks :
+
+Create a new branch and one or few commits: in these improve something but also
+deliberately introduce a typo and also a larger mistake which we will want to fix during the code review.
+Open a pull request towards the main branch.
+As a reviewer to somebody else’s pull request, ask for an improvement and
+also directly suggest a change for the small typo. (Hint:
+suggestions are possible through the GitHub web interface, view of
+a pull request, “Files changed” view, after selecting some lines.
+Look for the “±” button.)
+As the submitter, learn how to accept the suggested change. (Hint:
+GitHub web interface, “Files Changed” view.)
+As the submitter, improve the pull request without having to close and open
+a new one: by adding a new commit to the same branch. (Hint: push
+to the branch again.)
+Once the changes are addressed, merge the pull request.
+
+
+
+
+Help and discussion
+From here on out, we don’t give detailed steps to the solution. You
+need to combine what you know, and the extra info below, in order to
+solve the above.
+
+How to ask for changes in a pull request
+Technically, there are at least two common ways to ask for changes in a pull
+request.
+Either in the comment field of the pull request:
+
+
+
+
+Or by using the “Review changes”:
+
+
+
+
+And always please be kind and constructive in your comments. Remember that the
+goal is not gate-keeping but collaborative learning .
+
+
+How to suggest a change in a pull request as a reviewer
+If you see a very small problem that is easy to fix, you can suggest a change
+as a reviewer.
+Instead of asking the submitter to tiny problem, you can suggest a change by
+clicking on the plus sign next to the line number in the “Files changed” tab:
+
+
+
+
+Here you can comment on specific lines or even line ranges.
+But now the interesting part is to click on the “Add a suggestion” symbol (the
+one that looks like plus and minus). Now you can fix the tiny problem (in this
+case a typo) and then click on the “Add single comment” button:
+
+
+
+
+The result is this and the submitter can accept the change with a single click:
+
+
+
+
+After accepting with “Commit suggestion”, the improvement gets added to the
+pull request.
+
+
+
+
+Summary
+
+Our process isn’t just about code now. It’s about discussion and
+working together to make the whole process better.
+GitHub (or GitLab) discussions and reviewing are quite powerful and can make
+small changes easy.
+
+
+
+
+How to contribute changes to repositories that belong to others
+In this episode we prepare you to suggest and contribute changes to
+repositories that belong to others. These might be open source projects that
+you use in your work.
+We will see how Git and services like GitHub or GitLab can be used to suggest
+modification without having to ask for write access to the repository and
+accept modifications without having to grant write access to others.
+
+Exercise
+
+
Exercise preparation
+
+The exercise repository is now different:
+https://github.com/workshop-material/recipe-book-forking-exercise (note the -forking-exercise ).
+First fork the exercise repository to your GitHub account.
+Then clone your fork to your computer (if you wish to work locally).
+Double-check that you have forked the correct repository.
+
+
+
+
Exercise: Collaborating within the same repository (25 min)
+
Technical requirements :
+
+
What is familiar from previous lessons:
+
+
What will be new in this exercise:
+
+Opening a pull request towards the upstream repository.
+Pull requests can be coupled with automated testing.
+Learning that your fork can get out of date.
+After the pull requests are merged, updating your fork with the changes.
+Learn how to approach other people’s repositories with ideas, changes, and requests.
+
+
Exercise tasks :
+
+Open an issue in the upstream exercise repository where you describe the
+change you want to make. Take note of the issue number.
+Create a new branch in your fork of the repository.
+Make a change to the recipe book on the new branch and in the commit cross-reference the issue you opened.
+See the walk-through below for how to do this.
+Open a pull request towards the upstream repository.
+The instructor will review and merge the pull requests.
+During the review, pay attention to the automated test step (here for
+demonstration purposes, we test whether the recipe contains an ingredients
+and an instructions sections).
+After few pull requests are merged, update your fork with the changes.
+Check that in your fork you can see changes from other people’s pull requests.
+
+
+
+
+Help and discussion
+
+Help! I don’t have permissions to push my local changes
+Maybe you see an error like this one:
+ Please make sure you have the correct access rights
+and the repository exists.
+
+
+Or like this one:
+ failed to push some refs to workshop-material/recipe-book-forking-exercise.git
+
+
+In this case you probably try to push the changes not to your fork but to the original repository
+and in this exercise you do not have write access to the original repository.
+The simpler solution is to clone again but this time your fork.
+
+
Recovery
+
But if you want to keep your local changes, you can change the remote URL to point to your fork.
+Check where your remote points to with git remote --verbose
.
+
It should look like this (replace USER
with your GitHub username):
+
$ git remote --verbose
+
+origin git@github.com:USER/recipe-book-forking-exercise.git (fetch)
+origin git@github.com:USER/recipe-book-forking-exercise.git (push)
+
+
+
It should not look like this:
+
$ git remote --verbose
+
+origin git@github.com:workshop-material/recipe-book-forking-exercise.git (fetch)
+origin git@github.com:workshop-material/recipe-book-forking-exercise.git (push)
+
+
+
In this case you can adjust “origin” to point to your fork with:
+
$ git remote set-url origin git@github.com:USER/recipe-book-forking-exercise.git
+
+
+
+
+
+Opening a pull request towards the upstream repository
+We have learned in the previous episode that pull requests are always from
+branch to branch. But the branch can be in a different repository.
+When you open a pull request in a fork, by default GitHub will suggest to
+direct it towards the default branch of the upstream repository.
+This can be changed and it should always be verified, but in this case this is
+exactly what we want to do, from fork towards upstream:
+
+
+
+
+
+
+Pull requests can be coupled with automated testing
+We added an automated test here just for fun and so that you see that this is
+possible to do.
+In this exercise, the test is silly. It will check whether the recipe contains
+both an ingredients and an instructions section.
+In this example the test failed:
+
+
+
+
+Click on the “Details” link to see the details of the failed test:
+
+
+
+
+How can this be useful?
+
+The project can define what kind of tests are expected to pass before a pull
+request can be merged.
+The reviewer can see the results of the tests, without having to run them
+locally.
+
+How does it work?
+
+What tests or steps can you image for your project to run automatically with
+each pull request?
+
+
+How to update your fork with changes from upstream
+This used to be difficult but now it is two mouse clicks.
+Navigate to your fork and notice how GitHub tells you that your fork is behind.
+In my case, it is 9 commits behind upstream. To fix this, click on “Sync fork”
+and then “Update branch”:
+
+
+
+
+After the update my “branch is up to date” with the upstream repository:
+
+
+
+
+
+
+How to approach other people’s repositories with ideas, changes, and requests
+Contributing very minor changes
+
+If you observe an issue and have an idea how to fix it
+
+Open an issue in the repository you wish to contribute to
+Describe the problem
+If you have a suggestion on how to fix it, describe your suggestion
+Possibly discuss and get feedback
+If you are working on the fix, indicate it in the issue so that others know that somebody is working on it and who is working on it
+Submit your fix as pull request or merge request which references/closes the issue
+
+
+If you have an idea for a new feature
+
+Open an issue in the repository you wish to contribute to
+In the issue, write a short proposal for your suggested change or new feature
+Motivate why and how you wish to do this
+Also indicate where you are unsure and where you would like feedback
+Discuss and get feedback before you code
+Once you start coding, indicate that you are working on it
+Once you are done, submit your new feature as pull request or merge request which references/closes the issue/proposal
+
+
+
Motivation
+
+Get agreement and feedback before writing 5000 lines of code which might be rejected
+If we later wonder why something was done, we have the issue/proposal as
+reference and can read up on the reasoning behind a code change
+
+
+
+
+
+Summary
+
+This forking workflow lets you propose changes to repositories for
+which you have no write access .
+This is the way that much modern open-source software works.
+You can now contribute to any project you can view.
+
+
+
+
+
+
+Reproducible environments and dependencies
+
+
Objectives
+
+There are not many codes that have no dependencies.
+How should we deal with dependencies ?
+We will focus on installing and managing dependencies in Python when using packages from PyPI and Conda.
+We will not discuss how to distribute your code as a package.
+
+
+[This episode borrows from https://coderefinery.github.io/reproducible-python/reusable/
+and https://aaltoscicomp.github.io/python-for-scicomp/dependencies/ ]
+Essential XKCD comics:
+
+
+How to avoid: “It works on my machine 🤷”
+Use a standard way to list dependencies in your project:
+
+Python: requirements.txt
or environment.yml
+R: DESCRIPTION
or renv.lock
+Rust: Cargo.lock
+Julia: Project.toml
+C/C++/Fortran: CMakeLists.txt
or Makefile
or spack.yaml
or the module
+system on clusters or containers
+Other languages: …
+
+
+
+Two ecosystems: PyPI (The Python Package Index) and Conda
+
+
PyPI
+
+Installation tool: pip
or uv
or similar
+Traditionally used for Python-only packages or
+for Python interfaces to external libraries. There are also packages
+that have bundled external libraries (such as numpy).
+Pros:
+
+Easy to use
+Package creation is easy
+
+
+Cons:
+
+
+
+
+
+
Conda
+
+Installation tool: conda
or mamba
or similar
+Aims to be a more general package distribution tool
+and it tries to provide not only the Python packages, but also libraries
+and tools needed by the Python packages.
+Pros:
+
+
+Cons:
+
+
+
+
+
+
+Conda ecosystem explained
+
+Anaconda is a distribution of conda packages
+made by Anaconda Inc. When using Anaconda remember to check that your
+situation abides with their licensing terms (see below).
+Anaconda has recently changed its licensing terms , which affects its
+use in a professional setting. This caused uproar among academia
+and Anaconda modified their position in
+this article .
+Main points of the article are:
+
+conda (installation tool) and community channels (e.g. conda-forge)
+are free to use.
+Anaconda repository and Anaconda’s channels in the community repository
+are free for universities and companies with fewer than 200 employees.
+Non-university research institutions and national laboratories need
+licenses.
+Miniconda is free, when it does not download Anaconda’s packages.
+Miniforge is not related to Anaconda, so it is free.
+
+For ease of use on sharing environment files, we recommend using
+Miniforge to create the environments and using conda-forge as the main
+channel that provides software.
+
+Major repositories/channels:
+
+Anaconda Repository
+houses Anaconda’s own proprietary software channels.
+Anaconda’s proprietary channels: main
, r
, msys2
and anaconda
.
+These are sometimes called defaults
.
+conda-forge is the largest open source
+community channel. It has over 28k packages that include open-source
+versions of packages in Anaconda’s channels.
+
+
+
+
+
+
+Best practice: Install dependencies into isolated environments
+
+For each project, create a separate environment .
+Don’t install dependencies globally for all projects. Sooner or later, different projects will have conflicting dependencies.
+Install them from a file which documents them at the same time
+Install dependencies by first recording them in requirements.txt
or
+environment.yml
and install using these files, then you have a trace
+(we will practice this later below).
+
+
+
Keypoints
+
If somebody asks you what dependencies you have in your project, you should be
+able to answer this question with a file .
+
In Python, the two most common ways to do this are:
+
+
You can export (“freeze”) the dependencies from your current environment into these files:
+
# inside a conda environment
+$ conda env export --from-history > environment.yml
+
+# inside a virtual environment
+$ pip freeze > requirements.txt
+
+
+
+
+
+How to communicate the dependencies as part of a report/thesis/publication
+Each notebook or script or project which depends on libraries should come with
+either a requirements.txt
or a environment.yml
, unless you are creating
+and distributing this project as Python package.
+
+Attach a requirements.txt
or a environment.yml
to your thesis.
+Even better: Put requirements.txt
or a environment.yml
in your Git repository along your code.
+Even better: Also binderize your analysis pipeline.
+
+
+
+Containers
+
+A container is like an operating system inside a file .
+“Building a container”: Container definition file (recipe) -> Container image
+This can be used with Apptainer /
+SingularityCE .
+
+Containers offer the following advantages:
+
+Reproducibility : The same software environment can be recreated on
+different computers. They force you to know and document all your dependencies .
+Portability : The same software environment can be run on different computers.
+Isolation : The software environment is isolated from the host system.
+“Time travel ”:
+
+
+
+
+
+How to install dependencies into environments
+Now we understand a bit better why and how we installed dependencies
+for this course in the Software install instructions .
+We have used Miniforge and the long command we have used was:
+$ mamba env create -n course -f https://raw.githubusercontent.com/coderefinery/reproducible-python-ml/main/software/environment.yml
+
+
+This command did two things:
+
+Create a new environment with name “course” (specified by -n
).
+Installed all dependencies listed in the environment.yml
file (specified by
+-f
), which we fetched directly from the web.
+Here
+you can browse it.
+
+For your own projects:
+
+Start by writing an environment.yml
of requirements.txt
file. They look like this:
+
+
+
environment.yml requirements.txt
name : course
+channels :
+ - conda-forge
+ - bioconda
+dependencies :
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - jupyterlab
+ - pytest
+ - scalene
+ - flit
+ - ruff
+ - icecream
+ - snakemake-minimal
+ - myst-parser
+ - sphinx
+ - sphinx-rtd-theme
+ - sphinx-autoapi
+ - sphinx-autobuild
+ - black
+ - isort
+ - pip
+ - pip :
+ - jupyterlab-code-formatter
+
+
+
click
+numpy
+pandas
+scipy
+altair
+vl - convert - python
+jupyterlab
+pytest
+scalene
+flit
+ruff
+icecream
+myst - parser
+sphinx
+sphinx - rtd - theme
+sphinx - autoapi
+sphinx - autobuild
+black
+isort
+jupyterlab - code - formatter
+
+
+
+
+Then set up an isolated environment and install the dependencies from the file into it:
+
+
+
Miniforge Pixi Virtual environment uv
+Create a new environment with name “myenv” from environment.yml
:
+$ conda env create -n myenv -f environment.yml
+
+
+Or equivalently:
+$ mamba env create -n myenv -f environment.yml
+
+
+
+Activate the environment:
+
+
+Run your code inside the activated virtual environment.
+
+
+
+
+Create a virtual environment by running (the second argument is the name
+of the environment and you can change it):
+
+
+Activate the virtual environment (how precisely depends on your operating
+system and shell).
+Install the dependencies:
+$ python -m pip install -r requirements.txt
+
+
+
+Run your code inside the activated virtual environment.
+
+
+
+
+Create a virtual environment by running (the second argument is the name
+of the environment and you can change it):
+
+
+Activate the virtual environment (how precisely depends on your operating
+system and shell).
+Install the dependencies:
+$ uv pip sync requirements.txt
+
+
+
+Run your code inside the virtual environment.
+$ uv run python example.py
+
+
+
+
+
+
+
+Updating environments
+What if you forgot a dependency? Or during the development of your project
+you realize that you need a new dependency? Or you don’t need some dependency anymore?
+
+Modify the environment.yml
or requirements.txt
file.
+Either remove your environment and create a new one, or update the existing one:
+
+
+
Miniforge Pixi Virtual environment uv
+
+
+Pinning package versions
+Let us look at the
+environment.yml
+which we used to set up the environment for this course.
+Dependencies are listed without version numbers. Should we pin the
+versions ?
+
+Both pip
and conda
ecosystems and all the tools that we have
+mentioned support pinning versions.
+It is possible to define a range of versions instead of precise versions.
+While your project is still in progress, I often use latest versions and do not pin them.
+When publishing the script or notebook, it is a good idea to pin the versions
+to ensure that the code can be run in the future.
+Remember that at some point in time you will face a situation where
+newer versions of the dependencies are no longer compatible with your
+software. At this point you’ll have to update your software to use the newer
+versions or to lock it into a place in time.
+
+
+
+Managing dependencies on a supercomputer
+
+Additional challenges:
+
+Storage quotas: Do not install dependencies in your home directory . A conda environment can easily contain 100k files.
+Network file systems struggle with many small files. Conda environments often contain many small files.
+
+
+Possible solutions:
+
+Try Pixi (modern take on managing Conda environments) and
+uv (modern take on managing virtual
+environments). Blog post: Using Pixi and uv on a supercomputer
+Install your environment on the fly into a scratch directory on local disk (not the network file system).
+Install your environment on the fly into a RAM disk/drive.
+Containerize your environment into a container image.
+
+
+
+
+
+
Keypoints
+
+Being able to communicate your dependencies is not only nice for others, but
+also for your future self or the next PhD student or post-doc.
+If you ask somebody to help you with your code, they will ask you for the
+dependencies.
+
+
+
+
+
+Automated testing
+
+
Objectives
+
+Know where to start in your own project.
+Know what possibilities and techniques are available in the Python world.
+Have an example for how to make the testing part of code review .
+
+
+
+
Instructor note
+
+(15 min) Motivation
+(15 min) End-to-end tests
+(15 min) Pytest
+(15 min) Adding the unit test to GitHub Actions
+(10 min) What else is possible
+(20 min) Exercise
+
+
+
+Motivation
+Testing is a way to check that the code does what it is expected to.
+
+Less scary to change code : tests will tell you whether something broke.
+Easier for new people to join.
+Easier for somebody to revive an old code .
+End-to-end test : run the whole code and compare result to a reference.
+Unit tests : test one unit (function or module). Can guide towards better
+structured code: complicated code is more difficult to test.
+
+
+
+How testing is often taught
+def add ( a , b ):
+ return a + b
+
+
+def test_add ():
+ assert add ( 1 , 2 ) == 3
+
+
+How this feels:
+
+
+
+
+[Citation needed]
+
+
+Instead, we will look at and discuss a real example where we test components
+from our example project.
+
+
+Where to start
+Do I even need testing? :
+
+If you have nothing yet :
+
+Start with an end-to-end test.
+Describe in words how you check whether the code still works.
+Translate the words into a script (any language).
+Run the script automatically on every code change (GitHub Actions or GitLab CI).
+
+If you want to start with unit-testing :
+
+You want to rewrite a function? Start adding a unit test right there first.
+You spend few days chasing a bug? Once you fix it, add a test to make sure it does not come back.
+
+
+
+
+Pytest
+Here is a simple example of a test:
+def fahrenheit_to_celsius ( temp_f ):
+ """Converts temperature in Fahrenheit
+ to Celsius.
+ """
+ temp_c = ( temp_f - 32.0 ) * ( 5.0 / 9.0 )
+ return temp_c
+
+
+# this is the test function
+def test_fahrenheit_to_celsius ():
+ temp_c = fahrenheit_to_celsius ( temp_f = 100.0 )
+ expected_result = 37.777777
+ # assert raises an error if the condition is not met
+ assert abs ( temp_c - expected_result ) < 1.0e-6
+
+
+To run the test(s):
+
+Explanation: pytest
will look for functions starting with test_
in files
+and directories given as arguments. It will run them and report the results.
+Good practice to add unit tests:
+
+Add the test function and run it.
+Break the function on purpose and run the test.
+Does the test fail as expected?
+
+
+
+Adding the unit test to GitHub Actions
+Our next goal is that we want GitHub to run the unit test
+automatically on every change.
+First we need to extend our
+environment.yml :
+name : classification-task
+channels :
+ - conda-forge
+dependencies :
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - pytest
+
+
+We also need to extend .github/workflows/test.yml
(highlighted line):
+name : Test
+
+on :
+ push :
+ branches : [ main ]
+ pull_request :
+ branches : [ main ]
+
+jobs :
+ build :
+ runs-on : ubuntu-24.04
+
+ steps :
+ - name : Checkout
+ uses : actions/checkout@v4
+
+ - uses : mamba-org/setup-micromamba@v1
+ with :
+ micromamba-version : '2.0.5-0' # any version from https://github.com/mamba-org/micromamba-releases
+ environment-file : environment.yml
+ init-shell : bash
+ cache-environment : true
+ post-cleanup : 'all'
+ generate-run-shell : false
+
+ - name : Run tests
+ run : |
+ ./test.sh
+ pytest generate-predictions.py
+ shell : bash -el {0}
+
+
+In the above example, we assume that we added a test function to generate-predictions.py
.
+If we have time, we can try to create a pull request which would break the
+code and see how the test fails.
+
+
+What else is possible
+
+Run the test set automatically on every code change:
+
+
+The testing above used example-based testing.
+Test coverage : how much of the code is traversed by tests?
+
+
+Property-based testing: generates arbitrary data matching your specification and checks that your guarantee still holds in that case.
+
+
+Snapshot-based testing: makes it easier to generate snapshots for regression tests.
+
+
+Mutation testing : tests pass -> change a line of code (make a mutant) -> test again and check whether all mutants get “killed”.
+
+
+
+
+
+Exercises
+
+
Exercise
+
Experiment with the example project and what we learned above or try it on
+the example project or on your own project :
+
+Add a unit test. If you are unsure where to start , you can try to move
+the majority
+vote
+into a separate function and write a test function for it.
+Try to run pytest locally.
+Check whether it fails when you break the corresponding function.
+Try to run it on GitHub Actions.
+Create a pull request which would break the code and see whether the automatic test would catch it.
+Try to design an end-to-end test for your project. Already the thought
+process can be very helpful.
+
+
+
+
+
+
+Sharing notebooks
+
+[this lesson is adapted after https://coderefinery.github.io/jupyter/sharing/ ]
+
+Document dependencies
+
+If you import libraries into your notebook, note down their versions.
+Document the dependencies as discussed in section Reproducible environments and dependencies .
+Place either environment.yml
or requirements.txt
in the same folder as
+the notebook(s).
+If you publish the notebook as part of a publication, it is probably a good
+idea to pin the versions of the libraries you use.
+This is not only useful for people who will try to rerun this in future, it
+is also understood by some tools (e.g. Binder ) which we
+will see later.
+
+
+
+Different ways to share a notebook
+We need to learn how to share notebooks. At the minimum we need
+to share them with our future selves (backup and reproducibility).
+
+The following platforms can be used free of charge but have paid subscriptions for
+faster access to cloud resources:
+
+
+
+Sharing dynamic notebooks using Binder
+
+
Exercise/demo: Making your notebooks reproducible by anyone (15 min)
+
Instructor demonstrates this:
+
+First we look at the statically rendered version of the example
+notebook
+on GitHub and also nbviewer .
+Visit https://mybinder.org :
+
+
+
+
+Check that your notebook repository now has a “launch binder”
+badge in your README.md
file on GitHub.
+Try clicking the button and see how your repository is launched
+on Binder (can take a minute or two). Your notebooks can now be
+explored and executed in the cloud.
+Enjoy being fully reproducible!
+
+
+
+
+How to get a digital object identifier (DOI)
+
+Zenodo is a great service to get a
+DOI for a notebook
+(but first practice with the Zenodo sandbox ).
+Binder can also run notebooks from Zenodo.
+In the supporting information of your paper you can refer to its DOI.
+
+
+
+
+Concepts in refactoring and modular code design
+
+Starting questions for the collaborative document
+
+What does “modular code development” mean for you?
+What best practices can you recommend to arrive at well structured,
+modular code in your favourite programming language?
+What do you know now about programming that you wish somebody told you
+earlier?
+Do you design a new code project on paper before coding? Discuss pros and
+cons.
+Do you build your code top-down (starting from the big picture) or
+bottom-up (starting from components)? Discuss pros and cons.
+Would you prefer your code to be 2x slower if it was easier to read and
+understand?
+
+
+
+Pure functions
+
+Pure functions have no notion of state: They take input values and return
+values
+Given the same input, a pure function always returns the same value
+Function calls can be optimized away
+Pure function == data
+
+a) pure: no side effects
+def fahrenheit_to_celsius ( temp_f ):
+ temp_c = ( temp_f - 32.0 ) * ( 5.0 / 9.0 )
+ return temp_c
+
+temp_c = fahrenheit_to_celsius ( temp_f = 100.0 )
+print ( temp_c )
+
+
+b) stateful: side effects
+f_to_c_offset = 32.0
+f_to_c_factor = 0.555555555
+temp_c = 0.0
+
+def fahrenheit_to_celsius_bad ( temp_f ):
+ global temp_c
+ temp_c = ( temp_f - f_to_c_offset ) * f_to_c_factor
+
+fahrenheit_to_celsius_bad ( temp_f = 100.0 )
+print ( temp_c )
+
+
+Pure functions are easier to:
+
+Test
+Understand
+Reuse
+Parallelize
+Simplify
+Optimize
+Compose
+
+Mathematical functions are pure:
+
+\[f(x, y) = x - x^2 + x^3 + y^2 + xy\]
+
+\[(f \circ g)(x) = f(g(x))\]
+Unix shell commands are stateless:
+ $ cat somefile | grep somestring | sort | uniq | ...
+
+
+
+
+But I/O and network and disk and databases are not pure!
+
+
+
+
+
+
+From classes to functions
+Object-oriented programming and functional programming both have their place
+and value .
+Here is an example of expressing the same thing in Python in 4 different ways.
+Which one do you prefer?
+
+As a class :
+import math
+
+
+class Moon :
+ def __init__ ( self , name , radius , contains_water = False ):
+ self . name = name
+ self . radius = radius # in kilometers
+ self . contains_water = contains_water
+
+ def surface_area ( self ) -> float :
+ """Calculate the surface area of the moon assuming a spherical shape."""
+ return 4.0 * math . pi * self . radius ** 2
+
+ def __repr__ ( self ):
+ return f "Moon(name= { self . name !r} , radius= { self . radius } , contains_water= { self . contains_water } )"
+
+
+europa = Moon ( name = "Europa" , radius = 1560.8 , contains_water = True )
+
+print ( europa )
+print ( f "Surface area (km^2) of { europa . name } : { europa . surface_area () } " )
+
+
+
+As a dataclass :
+from dataclasses import dataclass
+import math
+
+
+@dataclass
+class Moon :
+ name : str
+ radius : float # in kilometers
+ contains_water : bool = False
+
+ def surface_area ( self ) -> float :
+ """Calculate the surface area of the moon assuming a spherical shape."""
+ return 4.0 * math . pi * self . radius ** 2
+
+
+europa = Moon ( name = "Europa" , radius = 1560.8 , contains_water = True )
+
+print ( europa )
+print ( f "Surface area (km^2) of { europa . name } : { europa . surface_area () } " )
+
+
+
+As a named tuple :
+import math
+from collections import namedtuple
+
+
+def surface_area ( radius : float ) -> float :
+ return 4.0 * math . pi * radius ** 2
+
+
+Moon = namedtuple ( "Moon" , [ "name" , "radius" , "contains_water" ])
+
+
+europa = Moon ( name = "Europa" , radius = 1560.8 , contains_water = True )
+
+print ( europa )
+print ( f "Surface area (km^2) of { europa . name } : { surface_area ( europa . radius ) } " )
+
+
+
+As a dict :
+import math
+
+
+def surface_area ( radius : float ) -> float :
+ return 4.0 * math . pi * radius ** 2
+
+
+europa = { "name" : "Europa" , "radius" : 1560.8 , "contains_water" : True }
+
+
+print ( europa )
+print ( f "Surface area (km^2) of { europa [ 'name' ] } : { surface_area ( europa [ 'radius' ]) } " )
+
+
+
+
+
+
+How to design your code before writing it
+
+
+
+
+
+Profiling
+
+
Objectives
+
+Understand when improving code performance is worth the time and effort.
+Knowing how to find performance bottlenecks in Python code.
+Try Scalene as one of many tools
+to profile Python code.
+
+
+[This page is adapted after https://aaltoscicomp.github.io/python-for-scicomp/profiling/ ]
+
+Should we even optimize the code?
+Classic quote to keep in mind: “Premature optimization is the root of all evil.” [Donald Knuth]
+
+
Discussion
+
It is important to ask ourselves whether it is worth it.
+
+
Depends. What does it depend on?
+
+
+
+Measure instead of guessing
+Before doing code surgery to optimize the run time or lower the memory usage,
+we should measure where the bottlenecks are. This is called profiling .
+Analogy: Medical doctors don’t start surgery based on guessing. They first measure
+(X-ray, MRI, …) to know precisely where the problem is.
+Not only programming beginners can otherwise guess wrong, but also experienced
+programmers can be surprised by the results of profiling.
+
+
+
+
+Tracing profilers vs. sampling profilers
+Tracing profilers record every function call and event in the program,
+logging the exact sequence and duration of events.
+
+Sampling profilers periodically samples the program’s state (where it is
+and how much memory is used), providing a statistical view of where time is
+spent.
+
+Pros:
+
+
+Cons:
+
+Less precise, potentially missing infrequent or short calls.
+Provides an approximation rather than exact timing.
+
+
+
+
+
Analogy: Imagine we want to optimize the London Underground (subway) system
+
We wish to detect bottlenecks in the system to improve the service and for this we have
+asked few passengers to help us by tracking their journey.
+
+Tracing : We follow every train and passenger, recording every stop
+and delay. When passengers enter and exit the train, we record the exact time
+and location.
+Sampling : Every 5 minutes the phone notifies the passenger to note
+down their current location. We then use this information to estimate
+the most crowded stations and trains.
+
+
+
+
+Choosing the right system size
+Sometimes we can configure the system size (for instance the time step in a simulation
+or the number of time steps or the matrix dimensions) to make the program finish sooner.
+For profiling, we should choose a system size that is representative of the real-world
+use case. If we profile a program with a small input size, we might not see the same
+bottlenecks as when running the program with a larger input size.
+Often, when we scale up the system size, or scale the number of processors, new bottlenecks
+might appear which we didn’t see before. This brings us back to: “measure instead of guessing”.
+
+
+Exercises
+
+
Exercise: Practicing profiling
+
In this exercise we will use the Scalene profiler to find out where most of the time is spent
+and most of the memory is used in a given code example.
+
Please try to go through the exercise in the following steps:
+
+Make sure scalene
is installed in your environment (if you have followed
+this course from the start and installed the recommended software
+environment, then it is).
+Download Leo Tolstoy’s “War and Peace” from the following link (the text is
+provided by Project Gutenberg ):
+https://www.gutenberg.org/cache/epub/2600/pg2600.txt
+(right-click and “save as” to download the file and save it as “book.txt” ).
+Before you run the profiler, try to predict in which function the code
+(the example code is below)
+will spend most of the time and in which function it will use most of the
+memory.
+Save the example code as example.py
and
+run the scalene
profiler on the following code example and browse the
+generated HTML report to find out where most of the time is spent and where
+most of the memory is used:
+
+Alternatively you can do this (and then open the generated file in a browser):
+$ scalene example.py --html > profile.html
+
+
+You can find an example of the generated HTML report in the solution below.
+
+Does the result match your prediction? Can you explain the results?
+
+
Example code (example.py
):
+
"""
+The code below reads a text file and counts the number of unique words in it
+(case-insensitive).
+"""
+import re
+
+
+def count_unique_words1 ( file_path : str ) -> int :
+ with open ( file_path , "r" , encoding = "utf-8" ) as file :
+ text = file . read ()
+ words = re . findall ( r "\b\w+\b" , text . lower ())
+ return len ( set ( words ))
+
+
+def count_unique_words2 ( file_path : str ) -> int :
+ unique_words = []
+ with open ( file_path , "r" , encoding = "utf-8" ) as file :
+ for line in file :
+ words = re . findall ( r "\b\w+\b" , line . lower ())
+ for word in words :
+ if word not in unique_words :
+ unique_words . append ( word )
+ return len ( unique_words )
+
+
+def count_unique_words3 ( file_path : str ) -> int :
+ unique_words = set ()
+ with open ( file_path , "r" , encoding = "utf-8" ) as file :
+ for line in file :
+ words = re . findall ( r "\b\w+\b" , line . lower ())
+ for word in words :
+ unique_words . add ( word )
+ return len ( unique_words )
+
+
+def main ():
+ # book.txt is downloaded from https://www.gutenberg.org/cache/epub/2600/pg2600.txt
+ _result = count_unique_words1 ( "book.txt" )
+ _result = count_unique_words2 ( "book.txt" )
+ _result = count_unique_words3 ( "book.txt" )
+
+
+if __name__ == "__main__" :
+ main ()
+
+
+
+
Solution
+
+
+
+
+Result of the profiling run for the above code example. You can click on the image to make it larger.
+
+
+
Results:
+
+
Explanation:
+
+The count_unique_words2
function is the slowest because it uses a list
+to store unique words and checks if a word is already in the list before
+adding it.
+Checking whether a list contains an element might require traversing the
+whole list, which is an O(n) operation. As the list grows in size,
+the lookup time increases with the size of the list.
+The count_unique_words1
and count_unique_words3
functions are faster
+because they use a set to store unique words.
+Checking whether a set contains an element is an O(1) operation.
+The count_unique_words1
function uses the most memory because it creates
+a list of all words in the text file and then creates a set from that
+list.
+The count_unique_words3
function uses less memory because it traverses
+the text file line by line instead of reading the whole file into memory.
+
+
What we can learn from this exercise:
+
+When processing large files, it can be good to read them line by line
+or in batches
+instead of reading the whole file into memory.
+It is good to get an overview over standard data structures and their
+advantages and disadvantages (e.g. adding an element to a list is fast but checking whether
+it already contains the element can be slow).
+
+
+
+
+
+Additional resources
+
+
+
+
+Choosing a software license
+
+
Objectives
+
+Knowing about what derivative work is and whether we can share it.
+Get familiar with terminology around licensing.
+We will add a license to our example project.
+
+
+
+Copyright and derivative work: Sampling/remixing
+
+
+
+
+[Midjourney, CC-BY-NC 4.0]
+
+
+
+
+[Midjourney, CC-BY-NC 4.0]
+
+Copyright controls whether and how we can distribute
+the original work or the derivative work .
+In the context of software it is more about
+being able to change and distribute changes .
+Changing and distributing software is similar to changing and distributing
+music
+You can do almost anything if you don’t distribute it
+
+Often we don’t have the choice :
+
+Can we distribute our changes with the research community or our future selves?
+
+
+Why software licenses matter
+You find some great code that you want to reuse for your own publication.
+
+This is good for the original author - you will cite them. Maybe other people who cite you will cite them.
+You modify and remix the code.
+Two years later … ⌛
+Time to publish: You realize there is no license to the original work 😱
+
+Now we have a problem :
+
+😬 “Best” case: You manage to publish the paper without the software/data.
+Others cannot build on your software and data.
+😱 Worst case: You cannot publish it at all.
+Journal requires that papers should come with data and software so that they are reproducible.
+
+
+
+Taxonomy of software licenses
+
+
+
+European Commission, Directorate-General for Informatics, Schmitz, P., European Union Public Licence (EUPL): guidelines July 2021, Publications Office, 2021, https://data.europa.eu/doi/10.2799/77160
+
+
+Comments:
+
+Arrows represent compatibility (A -> B: B can reuse A)
+Proprietary/custom: Derivative work typically not possible (no arrow goes from proprietary to open)
+Permissive: Derivative work does not have to be shared
+Copyleft/reciprocal: Derivative work must be made available under the same license terms
+NC (non-commercial) and ND (non-derivative) exist for data licenses but not really for software licenses
+
+Great resource for comparing software licenses : Joinup Licensing Assistant
+
+
+
+Exercise/demo
+
+
+
Discussion
+
+What if my code uses libraries like numpy
, pandas
, scipy
,
+altair
, etc. Do we need to look at their licenses? In other words,
+is our project derivative work of something else?
+
+
+
+
+
+More exercises
+
+
Exercise: What constitutes derivative work?
+
Which of these are derivative works? Also reflect/discuss how this affects the
+choice of license.
+
+A. Download some code from a website and add on to it
+B. Download some code and use one of the functions in your code
+C. Changing code you got from somewhere
+D. Extending code you got from somewhere
+E. Completely rewriting code you got from somewhere
+F. Rewriting code to a different programming language
+G. Linking to libraries (static or dynamic), plug-ins, and drivers
+H. Clean room design (somebody explains you the code but you have never seen it)
+I. You read a paper, understand algorithm, write own code
+
+
+
+
+
Exercise: Licensing situations
+
Consider some common licensing situations. If you are part of an exercise
+group, discuss these with others:
+
+What is the StackOverflow license for code you copy and paste?
+A journal requests that you release your software during publication. You have
+copied a portion of the code from another package, which you have forgotten.
+Can you satisfy the journal’s request?
+You want to fix a bug in a project someone else has released, but there is no license. What risks are there?
+How would you ask someone to add a license?
+You incorporate MIT, GPL, and BSD3 licensed code into your project. What possible licenses can you pick for your project?
+You do the same as above but add in another license that looks strong copyleft. What possible licenses can you use now?
+Do licenses apply if you don’t distribute your code? Why or why not?
+Which licenses are most/least attractive for companies with proprietary software?
+
+
+
Solution
+
+As indicated here , all publicly accessible user contributions are licensed under Creative Commons Attribution-ShareAlike license. See Stackoverflow Terms of service for more detailed information.
+“Standard” licensing rules apply. So in this case, you would need to remove the portion of code you have copied from another package before being able to release your software.
+By default you are no authorized to use the content of a repository when there is no license. And derivative work is also not possible by default. Other risks: it may not be clear whether you can use and distribute (publish) the bugfixed code. For the repo owners it may not be clear whether they can use and distributed the bugfixed code. However, the authors may have forgotten to add a license so we suggest you to contact the authors (e.g. make an issue) and ask whether they are willing to add a license.
+As mentionned in 3., the easiest is to fill an issue and explain the reasons why you would like to use this software (or update it).
+Combining software with different licenses can be tricky and it is important to understand compatibilities (or lack of compatibilities) of the various licenses. GPL license is the most protective (BSD and MIT are quite permissive) so for the resulting combined software you could use a GPL license. However, re-licensing may not be necessary.
+Derivative work would need to be shared under this strong copyleft license (e.g. AGPL or GPL), unless the components are only plugins or libraries.
+If you keep your code for yourself, you may think you do not need a license. However, remember that in most companies/universities, your employer is “owning” your work and when you leave you may not be allowed to “distribute your code to your future self”. So the best is always to add a license!
+The least attractive licenses for companies with proprietary software are licenses where you would need to keep an open license when creating derivative work. For instance GPL and and AGPL. The most attractive licenses are permissive licenses where they can reuse, modify and relicense with no conditions. For instance MIT, BSD and Apache License.
+
+
+
+
+
+
+How to publish your code
+
+
+Is putting software on GitHub/GitLab/… publishing?
+
+
+
+
+FAIR principles. (c) Scriberia for The Turing Way , CC-BY.
+
+
+Is it enough to make the code public for the code to remain findable and accessible ?
+
+No. Because nothing prevents me from deleting my GitHub repository or
+rewriting the Git history and we have no guarantee that GitHub will still be around in 10 years.
+Make your code citable and persistent :
+Get a persistent identifier (PID) such as DOI in addition to sharing the
+code publicly, by using services like Zenodo or
+similar services.
+
+
+
+How to make your software citable
+
+
Discussion: Explain how you currently cite software
+
+Do you cite software that you use? How?
+If I wanted to cite your code/scripts, what would I need to do?
+
+
+Checklist for making a release of your software citable :
+
+Assigned an appropriate license
+Described the software using an appropriate metadata format
+Clear version number
+Authors credited
+Procured a persistent identifier
+Added a recommended citation to the software documentation
+
+This checklist is adapted from: N. P. Chue Hong, A. Allen, A. Gonzalez-Beltran,
+et al., Software Citation Checklist for Developers (Version 0.9.0). Zenodo.
+2019b. (DOI )
+Our practical recommendations :
+
+This is an example of a simple CITATION.cff
file:
+cff-version : 1.2.0
+message : "If you use this software, please cite it as below."
+authors :
+ - family-names : Doe
+ given-names : Jane
+ orcid : https://orcid.org/1234-5678-9101-1121
+title : "My Research Software"
+version : 2.0.4
+doi : 10.5281/zenodo.1234
+date-released : 2021-08-11
+
+
+More about CITATION.cff
files:
+
+
+
+
+How to cite software
+
+
Great resources
+
+A. M. Smith, D. S. Katz, K. E. Niemeyer, and FORCE11 Software Citation
+Working Group, “Software citation principles,” PeerJ Comput. Sci., vol. 2,
+no. e86, 2016 (DOI )
+D. S. Katz, N. P. Chue Hong, T. Clark, et al., Recognizing the value of
+software: a software citation guide [version 2; peer review: 2 approved].
+F1000Research 2021, 9:1257 (DOI )
+N. P. Chue Hong, A. Allen, A. Gonzalez-Beltran, et al., Software Citation
+Checklist for Authors (Version 0.9.0). Zenodo. 2019a. (DOI )
+N. P. Chue Hong, A. Allen, A. Gonzalez-Beltran, et al., Software Citation
+Checklist for Developers (Version 0.9.0). Zenodo. 2019b. (DOI )
+
+
+Recommended format for software citation is to ensure the following information
+is provided as part of the reference (from Katz, Chue Hong, Clark,
+2021 which also contains
+software citation examples):
+
+Creator
+Title
+Publication venue
+Date
+Identifier
+Version
+Type
+
+
+
+Exercise/demo
+
+
Exercise
+
+We will add a CITATION.cff
file to our example repository.
+We will get a DOI using the Zenodo sandbox :
+
+
+We can try to create an example repository with a Jupyter Notebook and run it through Binder
+to make it persistent and citable.
+
+
+
+
+
+
+
+Creating a Python package and deploying it to PyPI
+
+
Objectives
+
In this episode we will create a pip-installable Python package and learn how
+to deploy it to PyPI. As example, we can use one of the Python scripts from our
+example repository.
+
+
+Creating a Python package with the help of Flit
+There are unfortunately many ways to package a Python project:
+
+setuptools
is the most common way to package a Python project. It is very
+powerful and flexible, but also can get complex.
+flit
is a simpler alternative to setuptools
. It is less versatile, but
+also easier to use.
+poetry
is a modern packaging tool which is more versatile than flit
and
+also easier to use than setuptools
.
+twine
is another tool to upload packages to PyPI.
+…
+
+
+
This will be a demo
+
+We will try to package the code together on the big screen.
+We will share the result on GitHub so that you can retrace the steps.
+In this demo, we will use Flit to package the code. From Why use Flit? :
+
+Make the easy things easy and the hard things possible is an old motto from
+the Perl community. Flit is entirely focused on the easy things part of that,
+and leaves the hard things up to other tools.
+
+
+
+
+
+
+
+Step 2: Testing an install from GitHub
+If a local install worked, push the pyproject.toml
to GitHub and try to install the package from GitHub.
+In a requirements.txt
file, you can specify the GitHub repository and the
+branch (adapt the names):
+git + https : // github . com / ORGANIZATION / REPOSITORY . git @main
+
+
+A corresponding envionment.yml
file for conda would look like this (adapt the
+names):
+name : experiment
+channels :
+ - conda-forge
+dependencies :
+ - python <= 3.12
+ - pip
+ - pip :
+ - git+https://github.com/ORGANIZATION/REPOSITORY.git@main
+
+
+Does it install and run? If yes, move on to the next step (test-PyPI and later
+PyPI).
+
+
+Step 3: Deploy the package to test-PyPI using GitHub Actions
+Our final step is to create a GitHub Actions workflow which will run when we
+create a new release.
+
+
Danger
+
I recommend to first practice this on the test-PyPI before deploying to the
+real PyPI.
+
+Here is the workflow (.github/workflows/package.yml
):
+name : Package
+
+on :
+ release :
+ types : [ created ]
+
+jobs :
+ build :
+ permissions : write-all
+ runs-on : ubuntu-24.04
+
+ steps :
+ - name : Switch branch
+ uses : actions/checkout@v4
+ - name : Set up Python
+ uses : actions/setup-python@v5
+ with :
+ python-version : "3.12"
+ - name : Install Flit
+ run : |
+ pip install flit
+ - name : Flit publish
+ run :
+ flit publish
+ env :
+ FLIT_USERNAME : __token__
+ FLIT_PASSWORD : ${{ secrets.PYPI_TOKEN }}
+ # uncomment the following line if you are using test.pypi.org:
+# FLIT_INDEX_URL: https://test.pypi.org/legacy/
+ # this is how you can test the installation from test.pypi.org:
+# pip install --index-url https://test.pypi.org/simple/ package_name
+
+
+About the FLIT_PASSWORD: ${{ secrets.PYPI_TOKEN }}
:
+
+
+
+
+
+
+Credit
+This lesson is a mashup of the following sources (all CC-BY):
+
+The lesson uses the following example repository:
+
+The classification task has replaced the “planets” example repository used in
+the original lesson.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/singlehtml/objects.inv b/singlehtml/objects.inv
new file mode 100644
index 0000000..7e74d8f
Binary files /dev/null and b/singlehtml/objects.inv differ
diff --git a/software-licensing/index.html b/software-licensing/index.html
new file mode 100644
index 0000000..863a3c5
--- /dev/null
+++ b/software-licensing/index.html
@@ -0,0 +1,322 @@
+
+
+
+
+
+
+
+
+ Choosing a software license — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Choosing a software license
+
+
Objectives
+
+Knowing about what derivative work is and whether we can share it.
+Get familiar with terminology around licensing.
+We will add a license to our example project.
+
+
+
+Copyright and derivative work: Sampling/remixing
+
+
+
+
+[Midjourney, CC-BY-NC 4.0]
+
+
+
+
+[Midjourney, CC-BY-NC 4.0]
+
+Copyright controls whether and how we can distribute
+the original work or the derivative work .
+In the context of software it is more about
+being able to change and distribute changes .
+Changing and distributing software is similar to changing and distributing
+music
+You can do almost anything if you don’t distribute it
+
+Often we don’t have the choice :
+
+Can we distribute our changes with the research community or our future selves?
+
+
+Why software licenses matter
+You find some great code that you want to reuse for your own publication.
+
+This is good for the original author - you will cite them. Maybe other people who cite you will cite them.
+You modify and remix the code.
+Two years later … ⌛
+Time to publish: You realize there is no license to the original work 😱
+
+Now we have a problem :
+
+😬 “Best” case: You manage to publish the paper without the software/data.
+Others cannot build on your software and data.
+😱 Worst case: You cannot publish it at all.
+Journal requires that papers should come with data and software so that they are reproducible.
+
+
+
+Taxonomy of software licenses
+
+
+
+European Commission, Directorate-General for Informatics, Schmitz, P., European Union Public Licence (EUPL): guidelines July 2021, Publications Office, 2021, https://data.europa.eu/doi/10.2799/77160
+
+
+Comments:
+
+Arrows represent compatibility (A -> B: B can reuse A)
+Proprietary/custom: Derivative work typically not possible (no arrow goes from proprietary to open)
+Permissive: Derivative work does not have to be shared
+Copyleft/reciprocal: Derivative work must be made available under the same license terms
+NC (non-commercial) and ND (non-derivative) exist for data licenses but not really for software licenses
+
+Great resource for comparing software licenses : Joinup Licensing Assistant
+
+
+
+Exercise/demo
+
+
+
Discussion
+
+What if my code uses libraries like numpy
, pandas
, scipy
,
+altair
, etc. Do we need to look at their licenses? In other words,
+is our project derivative work of something else?
+
+
+
+
+
+More exercises
+
+
Exercise: What constitutes derivative work?
+
Which of these are derivative works? Also reflect/discuss how this affects the
+choice of license.
+
+A. Download some code from a website and add on to it
+B. Download some code and use one of the functions in your code
+C. Changing code you got from somewhere
+D. Extending code you got from somewhere
+E. Completely rewriting code you got from somewhere
+F. Rewriting code to a different programming language
+G. Linking to libraries (static or dynamic), plug-ins, and drivers
+H. Clean room design (somebody explains you the code but you have never seen it)
+I. You read a paper, understand algorithm, write own code
+
+
+
+
+
Exercise: Licensing situations
+
Consider some common licensing situations. If you are part of an exercise
+group, discuss these with others:
+
+What is the StackOverflow license for code you copy and paste?
+A journal requests that you release your software during publication. You have
+copied a portion of the code from another package, which you have forgotten.
+Can you satisfy the journal’s request?
+You want to fix a bug in a project someone else has released, but there is no license. What risks are there?
+How would you ask someone to add a license?
+You incorporate MIT, GPL, and BSD3 licensed code into your project. What possible licenses can you pick for your project?
+You do the same as above but add in another license that looks strong copyleft. What possible licenses can you use now?
+Do licenses apply if you don’t distribute your code? Why or why not?
+Which licenses are most/least attractive for companies with proprietary software?
+
+
+
Solution
+
+As indicated here , all publicly accessible user contributions are licensed under Creative Commons Attribution-ShareAlike license. See Stackoverflow Terms of service for more detailed information.
+“Standard” licensing rules apply. So in this case, you would need to remove the portion of code you have copied from another package before being able to release your software.
+By default you are no authorized to use the content of a repository when there is no license. And derivative work is also not possible by default. Other risks: it may not be clear whether you can use and distribute (publish) the bugfixed code. For the repo owners it may not be clear whether they can use and distributed the bugfixed code. However, the authors may have forgotten to add a license so we suggest you to contact the authors (e.g. make an issue) and ask whether they are willing to add a license.
+As mentionned in 3., the easiest is to fill an issue and explain the reasons why you would like to use this software (or update it).
+Combining software with different licenses can be tricky and it is important to understand compatibilities (or lack of compatibilities) of the various licenses. GPL license is the most protective (BSD and MIT are quite permissive) so for the resulting combined software you could use a GPL license. However, re-licensing may not be necessary.
+Derivative work would need to be shared under this strong copyleft license (e.g. AGPL or GPL), unless the components are only plugins or libraries.
+If you keep your code for yourself, you may think you do not need a license. However, remember that in most companies/universities, your employer is “owning” your work and when you leave you may not be allowed to “distribute your code to your future self”. So the best is always to add a license!
+The least attractive licenses for companies with proprietary software are licenses where you would need to keep an open license when creating derivative work. For instance GPL and and AGPL. The most attractive licenses are permissive licenses where they can reuse, modify and relicense with no conditions. For instance MIT, BSD and Apache License.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testing/index.html b/testing/index.html
new file mode 100644
index 0000000..6ae618d
--- /dev/null
+++ b/testing/index.html
@@ -0,0 +1,385 @@
+
+
+
+
+
+
+
+
+ Automated testing — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Automated testing
+
+
Objectives
+
+Know where to start in your own project.
+Know what possibilities and techniques are available in the Python world.
+Have an example for how to make the testing part of code review .
+
+
+
+
Instructor note
+
+(15 min) Motivation
+(15 min) End-to-end tests
+(15 min) Pytest
+(15 min) Adding the unit test to GitHub Actions
+(10 min) What else is possible
+(20 min) Exercise
+
+
+
+Motivation
+Testing is a way to check that the code does what it is expected to.
+
+Less scary to change code : tests will tell you whether something broke.
+Easier for new people to join.
+Easier for somebody to revive an old code .
+End-to-end test : run the whole code and compare result to a reference.
+Unit tests : test one unit (function or module). Can guide towards better
+structured code: complicated code is more difficult to test.
+
+
+
+How testing is often taught
+def add ( a , b ):
+ return a + b
+
+
+def test_add ():
+ assert add ( 1 , 2 ) == 3
+
+
+How this feels:
+
+
+
+
+[Citation needed]
+
+
+Instead, we will look at and discuss a real example where we test components
+from our example project.
+
+
+Where to start
+Do I even need testing? :
+
+If you have nothing yet :
+
+Start with an end-to-end test.
+Describe in words how you check whether the code still works.
+Translate the words into a script (any language).
+Run the script automatically on every code change (GitHub Actions or GitLab CI).
+
+If you want to start with unit-testing :
+
+You want to rewrite a function? Start adding a unit test right there first.
+You spend few days chasing a bug? Once you fix it, add a test to make sure it does not come back.
+
+
+
+
+Pytest
+Here is a simple example of a test:
+def fahrenheit_to_celsius ( temp_f ):
+ """Converts temperature in Fahrenheit
+ to Celsius.
+ """
+ temp_c = ( temp_f - 32.0 ) * ( 5.0 / 9.0 )
+ return temp_c
+
+
+# this is the test function
+def test_fahrenheit_to_celsius ():
+ temp_c = fahrenheit_to_celsius ( temp_f = 100.0 )
+ expected_result = 37.777777
+ # assert raises an error if the condition is not met
+ assert abs ( temp_c - expected_result ) < 1.0e-6
+
+
+To run the test(s):
+
+Explanation: pytest
will look for functions starting with test_
in files
+and directories given as arguments. It will run them and report the results.
+Good practice to add unit tests:
+
+Add the test function and run it.
+Break the function on purpose and run the test.
+Does the test fail as expected?
+
+
+
+Adding the unit test to GitHub Actions
+Our next goal is that we want GitHub to run the unit test
+automatically on every change.
+First we need to extend our
+environment.yml :
+name : classification-task
+channels :
+ - conda-forge
+dependencies :
+ - python <= 3.12
+ - click
+ - numpy
+ - pandas
+ - scipy
+ - altair
+ - vl-convert-python
+ - pytest
+
+
+We also need to extend .github/workflows/test.yml
(highlighted line):
+name : Test
+
+on :
+ push :
+ branches : [ main ]
+ pull_request :
+ branches : [ main ]
+
+jobs :
+ build :
+ runs-on : ubuntu-24.04
+
+ steps :
+ - name : Checkout
+ uses : actions/checkout@v4
+
+ - uses : mamba-org/setup-micromamba@v1
+ with :
+ micromamba-version : '2.0.5-0' # any version from https://github.com/mamba-org/micromamba-releases
+ environment-file : environment.yml
+ init-shell : bash
+ cache-environment : true
+ post-cleanup : 'all'
+ generate-run-shell : false
+
+ - name : Run tests
+ run : |
+ ./test.sh
+ pytest generate-predictions.py
+ shell : bash -el {0}
+
+
+In the above example, we assume that we added a test function to generate-predictions.py
.
+If we have time, we can try to create a pull request which would break the
+code and see how the test fails.
+
+
+What else is possible
+
+Run the test set automatically on every code change:
+
+
+The testing above used example-based testing.
+Test coverage : how much of the code is traversed by tests?
+
+
+Property-based testing: generates arbitrary data matching your specification and checks that your guarantee still holds in that case.
+
+
+Snapshot-based testing: makes it easier to generate snapshots for regression tests.
+
+
+Mutation testing : tests pass -> change a line of code (make a mutant) -> test again and check whether all mutants get “killed”.
+
+
+
+
+
+Exercises
+
+
Exercise
+
Experiment with the example project and what we learned above or try it on
+the example project or on your own project :
+
+Add a unit test. If you are unsure where to start , you can try to move
+the majority
+vote
+into a separate function and write a test function for it.
+Try to run pytest locally.
+Check whether it fails when you break the corresponding function.
+Try to run it on GitHub Actions.
+Create a pull request which would break the code and see whether the automatic test would catch it.
+Try to design an end-to-end test for your project. Already the thought
+process can be very helpful.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/version-control/branching-and-committing/index.html b/version-control/branching-and-committing/index.html
new file mode 100644
index 0000000..9f5479c
--- /dev/null
+++ b/version-control/branching-and-committing/index.html
@@ -0,0 +1,452 @@
+
+
+
+
+
+
+
+
+ Creating branches and commits — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Creating branches and commits
+The first and most basic task to do in Git is record changes using
+commits. In this part, we will record changes in two
+ways: on a new branch (which supports multiple lines of work at once), and directly
+on the “main” branch (which happens to be the default branch here).
+
+
Objectives
+
+Record new changes to our own copy of the project.
+Understand adding changes in two separate branches.
+See how to compare different versions or branches.
+
+
+
+Background
+
+In the previous episode we have browsed an existing repository and saw commits
+and branches .
+Each commit is a snapshot of the entire project at a certain
+point in time and has a unique identifier (hash ) .
+A branch is a line of development, and the main
branch or master
branch
+are often the default branch in Git.
+A branch in Git is like a sticky note that is attached to a commit . When we add
+new commits to a branch, the sticky note moves to the new commit.
+Tags are a way to mark a specific commit as important, for example a release
+version. They are also like a sticky note, but they don’t move when new
+commits are added.
+
+
+
+
+
+What if two people, at the same time, make two different changes? Git
+can merge them together easily. Image created using https://gopherize.me/
+(inspiration ).
+
+
+
+
+Exercise: Creating branches and commits
+
+
+
+
+Illustration of what we want to achieve in this exercise.
+
+
+
+
Exercise: Practice creating commits and branches (20 min)
+
+First create a new branch and then either add a new file or modify an
+existing file and commit the change. Make sure that you now work on your
+copy of the example repository. In your new commit you can share a Git or
+programming trick you like or improve the documentation.
+In a new commit, modify the file again.
+Switch to the main
branch and create a commit there.
+Browse the network and locate the commits that you just created (“Insights” -> “Network”).
+Compare the branch that you created with the main
branch. Can you find an easy way to see the differences?
+Can you find a way to compare versions between two arbitrary commits in the repository?
+Try to rename the branch that you created and then browse the network again.
+Try to create a tag for one of the commits that you created (on GitHub,
+create a “release”).
+Optional: If this was too easy, you can try to create a new branch and on
+this branch work on one of these new features:
+
+The random seed is now set to a specific number (42). Make it possible to
+set the seed to another number or to turn off the seed setting via the command line interface.
+Move the code that does the majority vote to an own function.
+Write a test for the new majority vote function.
+The num_neighbors
in the code needs to be odd. Change the code so that it stops with an error message if an even number is given.
+Add type annotations to some functions.
+When calling the scatter_plot
function, call the function with named arguments.
+Add example usage to README.md.
+Add a Jupyter Notebook version of the example code.
+
+
+
+
+The solution below goes over most of the answers, and you are
+encouraged to use it when the hints aren’t enough - this is by
+design.
+
+
+Solution and walk-through
+
+(1) Create a new branch and a new commit
+
+
GitHub VS Code Command line
+Where it says “main” at the top left, click, enter a new branch
+name (e.g. new-tutorial
), then click on
+“Create branch … from main”.
+Make sure you are still on the new-tutorial
branch (it should say
+it at the top), and click “Add file” → “Create new file” from the
+upper right.
+Enter a filename where it says “Name your file…”.
+Share some Git or programming trick you like.
+Click “Commit changes”
+Enter a commit message. Then click “Commit
+changes”.
+
+
You should appear back at the file browser view, and see your
+modification there.
+
+Make sure that you are on the main branch.
+Version control button on left sidebar → Three dots in upper right of source control → Branch → Create branch.
+VS Code automatically switches to the new branch.
+Create a new file.
+In the version control sidebar, click the +
sign to add the file for the next commit.
+Enter a brief message and click “Commit”.
+
+
Create a new branch called new-tutorial
from main
and switch to it:
+
$ git switch --create new-tutorial main
+
+
+
Then create the new file. Finally add and commit the file:
+
$ git add tutorial.md # or a different file name
+$ git commit -m "sharing a programming trick"
+
+
+
+
+
+(2) Modify the file again with a new commit
+
+
GitHub VS Code Command line
This is similar to before, but we click on the existing file to
+modify.
+
+Click on the file you added or modified previously.
+Click the edit button, the pencil icon at top-right.
+Follow the “Commit changes” instructions as in the previous step.
+
+
Repeat as in the previous step.
+
Modify the file. Then commit the new change:
+
$ git add tutorial.md
+$ git commit -m "short summary of the change"
+
+
+
Make sure to replace “short summary of the change” with a meaningful commit
+message.
+
+
+
+(3) Switch to the main branch and create a commit there
+
+
GitHub VS Code Command line
+Go back to the main repository page (your user’s page).
+In the branch switch view (top left above the file view), switch to
+main
.
+Modify another file that already exists, following the pattern
+from above.
+
+
Use the branch selector at the bottom to switch back to the main branch. Repeat the same steps as above,
+but this time modify a different file.
+
First switch to the main
branch:
+
+
Then modify a file. Finally git add
and then commit the change:
+
$ git commit -m "short summary of the change"
+
+
+
+
+
+(4) Browse the commits you just made
+Let’s look at what we did. Now, the main
and the new branches
+have diverged: both have some modifications. Try to find the commits
+you created.
+
+
GitHub VS Code Command line
Insights tab → Network view (just like we have done before).
+
This requires an extension. Opening the VS Code terminal lets you use the
+command line method (View → Terminal will open a terminal at bottom). This is
+a normal command line interface and very useful for work.
+
$ git graph
+$ git log --graph --oneline --decorate --all # if you didn't define git graph yet.
+
+
+
+
+
+(5) Compare the branches
+Comparing changes is an important thing we need to do. When using the
+GitHub view only, this may not be so common, but we’ll show it so that
+it makes sense later on.
+
+
GitHub VS Code Command line
A nice way to compare branches is to add /compare
to the URL of the repository,
+for example (replace USER):
+https://github.com/USER /classification-task/compare
+
This seems to require an extension. We recommend you use the command line method.
+
$ git diff main new-tutorial
+
+
+
Try also the other way around:
+
$ git diff new-tutorial main
+
+
+
Try also this if you only want to see the file names that are different:
+
$ git diff --name-only main new-tutorial
+
+
+
+
+
+(6) Compare two arbitrary commits
+This is similar to above, but not only between branches.
+
+
GitHub VS Code Command line
Following the /compare
-trick above, one can compare commits on GitHub by
+adjusting the following URL:
+https://github.com/USER /classification-task/compare/VERSION1 ..VERSION2
+
Replace USER
with your username and VERSION1
and VERSION2
with a commit hash or branch name.
+Please try it out.
+
Again, we recommend using the Command Line method.
+
First try this to get a short overview of the commits:
+
+
Then try to compare any two commit identifiers with git diff
.
+
+
+
+(7) Renaming a branch
+
+
GitHub VS Code Command line
Branch button → View all branches → three dots at right side → Rename branch.
+
Version control sidebar → Three dots (same as in step 2) → Branch → Rename branch. Make sure you are on the right branch before you start.
+
Renaming the current branch:
+
$ git branch -m new-branch-name
+
+
+
Renaming a different branch:
+
$ git branch -m different-branch new-branch-name
+
+
+
+
+
+(8) Creating a tag
+Tags are a way to mark a specific commit as important, for example a release
+version. They are also like a sticky note, but they don’t move when new
+commits are added.
+
+
GitHub VS Code Command line
On the right side, below “Releases”, click on “Create a new release”.
+
What GitHub calls releases are actually tags in Git with additional metadata.
+For the purpose of this exercise we can use them interchangeably.
+
Version control sidebar → Three dots (same as in step 2) → Tags → Create tag. Make sure you are on the expected commit before you do this.
+
Creating a tag:
+
$ git tag -a v1.0 -m "New manuscript version for the pre-print"
+
+
+
+
+
+
+Summary
+In this part, we saw how we can make changes to our files. With branches, we
+can track several lines of work at once, and can compare their differences.
+
+You could commit directly to main
if there is only one single line
+of work and it’s only you.
+You could commit to branches if there are multiple lines of work at
+once, and you don’t want them to interfere with each other.
+Tags are useful to mark a specific commit as important, for example a
+release version.
+In Git, commits form a so-called “graph”. Branches are tags in Git function
+like sticky notes that stick to specific commits. What this means for us is
+that it does not cost any significant disk space to create new branches.
+Not all files should be added to Git. For example, temporary files or
+files with sensitive information or files which are generated as part of
+the build process should not be added to Git. For this we use
+.gitignore
(more about this later: Practical advice: How much Git is necessary? ).
+Unsure on which branch you are or what state the repository is in?
+On the command line, use git status
frequently to get a quick overview.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/version-control/browsing/index.html b/version-control/browsing/index.html
new file mode 100644
index 0000000..5eaf387
--- /dev/null
+++ b/version-control/browsing/index.html
@@ -0,0 +1,467 @@
+
+
+
+
+
+
+
+
+ Forking, cloning, and browsing — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Forking, cloning, and browsing
+In this episode, we will look at an existing repository to understand how
+all the pieces work together. Along the way, we will make a copy (by
+forking and/or cloning ) of the repository for us, which will be used
+for our own changes.
+
+
Objectives
+
+See a real Git repository and understand what is inside of it.
+Understand how version control allows advanced inspection of a
+repository.
+See how Git allows multiple people to work on the same project at the same time.
+See the big picture instead of remembering a bunch of commands.
+
+
+
+GitHub, VS Code, or command line
+We offer three different paths for this exercise:
+
+GitHub (this is the one we will demonstrate)
+VS Code (if you prefer to follow along using an editor)
+Command line (for people comfortable with the command line)
+
+
+
+Creating a copy of the repository by “forking” or “cloning”
+A repository is a collection of files in one directory tracked by Git. A
+GitHub repository is GitHub’s copy, which adds things like access control,
+issue tracking, and discussions. Each GitHub repository is owned by a user or
+organization, who controls access.
+First, we need to make our own copy of the exercise repository. This will
+become important later, when we make our own changes.
+
+
+
+
+Illustration of forking a repository on GitHub.
+
+
+
+
+
+
+Illustration of cloning a repository to your computer.
+
+
+
+
+
+
+It is also possible to do this: to clone a forked repository to your computer.
+
+
+At all times you should be aware of if you are looking at your repository
+or the upstream repository (original repository):
+
+
+
How to create a fork
+
+Go to the repository view on GitHub: https://github.com/workshop-material/classification-task
+First, on GitHub, click the button that says “Fork”. It is towards
+the top-right of the screen.
+You should shortly be redirected to your copy of the repository
+USER/classification-task .
+
+
+
+
Instructor note
+
Before starting the exercise session show
+how to fork the repository to own account
+(above).
+
+
+
+Exercise: Copy and browse an existing project
+Work on this by yourself or in pairs.
+
+
Exercise preparation
+
+
GitHub VS Code Command line
In this case you will work on a fork.
+
You only need to open your own view, as described above. The browser
+URL should look like https://github.com/USER /classification-task, where
+USER is your GitHub username.
+
You need to have forked the repository as described above.
+
We need to start by making a clone of this repository so that you can work
+locally.
+
+Start VS Code.
+If you don’t have the default view (you already have a project
+open), go to File → New Window.
+Under “Start” on the screen, select “Clone Git Repository…”. Alternatively
+navigate to the “Source Control” tab on the left sidebar and click on the “Clone Repository” button.
+Paste in this URL: https://github.com/USER/classification-task
, where
+USER
is your username. You can copy this from the browser.
+Browse and select the folder in which you want to clone the
+repository.
+Say yes, you want to open this repository.
+Select “Yes, I trust the authors” (the other option works too).
+
+
This path is advanced and we do not include all command line
+information: you need to be somewhat
+comfortable with the command line already.
+
You need to have forked the repository as described above.
+
We need to start by making a clone of this repository so that you can work
+locally.
+
+Start the terminal in which you use Git (terminal application, or
+Git Bash).
+Change to the directory where you would want the repository to be
+(cd ~/code
for example, if the ~/code
directory is where you
+store your files).
+Run the following command: git clone https://github.com/USER/classification-task
, where USER
is your
+username. You might need to use a SSH clone command instead of
+HTTPS, depending on your setup.
+Change to that directory: cd classification-task
+
+
+
+
+
Exercise: Browsing an existing project (20 min)
+
Browse the example project and
+explore commits and branches, either on a fork or on a clone. Take notes and
+prepare questions. The hints are for the GitHub path in the browser.
+
+Browse the commit history : Are commit messages understandable?
+(Hint: “Commit history”, the timeline symbol, above the file list)
+Compare the commit history with the network graph (“Insights” -> “Network”). Can you find the branches?
+Try to find the history of commits for a single file , e.g. generate_predictions.py
.
+(Hint: “History” button in the file view)
+Which files include the word “training” ?
+(Hint: the GitHub search on top of the repository view)
+In the generate_predictions.py
file,
+find out who modified the evaluation of “majority_index”
+last and in which commit .
+(Hint: “Blame” view in the file view)
+Can you use this code yourself? Are you allowed to share
+modifications ?
+(Hint: look for a license file)
+
+
+The solution below goes over most of the answers, and you are
+encouraged to use it when the hints aren’t enough - this is by
+design.
+
+
+Solution and walk-through
+
+(1) Basic browsing
+The most basic thing to look at is the history of commits.
+
+This is visible from a button in the repository view. We see every
+change, when, and who has committed.
+Every change has a unique identifier, such as 79ce3be
. This can
+be used to identify both this change, and the whole project’s
+version as of that change.
+Clicking on a change in the view shows more.
+
+
+
GitHub VS Code Command line
Click on the timeline symbol in the repository view:
+
+
+
+
+
This can be done from “Timeline”, in the bottom of explorer, but only
+for a single file.
+
+
+
+(2) Compare commit history with network graph
+The commit history we saw above looks linear: one commit after
+another. But if we look at the network view, we see some branching and merging points.
+We’ll see how to do these later. This is another one of the
+basic Git views.
+
+
GitHub VS Code Command line
In a new browser tab, open the “Insights” tab, and click on “Network”.
+You can hover over the commit dots to see the person who committed and
+how they correspond with the commits in the other view:
+
+
+
+
+
We don’t know how to do this without an extension. Try starting a terminal and using the
+“Command line” option.
+
This is a useful command to browse the network of commits locally:
+
$ git log --graph --oneline --decorate --all
+
+
+
To avoid having to type this long command every time, you can define an alias (shortcut):
+
$ git config --global alias.graph "log --graph --oneline --decorate --all"
+
+
+
From then on, you can use git graph
to see the network graph.
+
+
+
+(3) How can you browse the history of a single file?
+We see the history for the whole repository, but we can also see it
+for a single file.
+
+
GitHub VS Code Command line
Navigate to the file view: Main page → generate_predictions.py.
+Click the “History” button near the top right.
+
Open generate_predictions.py file in the editor. Under the file browser,
+we see a “Timeline” view there.
+
The git log
command can take a filename and provide the log of only
+a single file:
+
$ git log generate_predictions.py
+
+
+
+
+
+(4) Which files include the word “training”?
+Version control makes it very easy to find all occurrences of a
+word or pattern. This is useful for things like finding where functions or
+variables are defined or used.
+
+
GitHub VS Code Command line
We go to the main file view. We click the Search magnifying
+class at the very top, type “training”, and click enter. We see every
+instance, including the context.
+
+
Searching in a forked repository will not work instantaneously!
+
It usually takes a few minutes before one can search for keywords in a forked repository
+since it first needs to build the search index the very first time we search.
+Start it, continue with other steps, then come back to this.
+
+
If you use the “Search” magnifying class on the left sidebar, and
+search for “training” it shows the occurrences in every file. You can
+click to see the usage in context.
+
grep
is the command line tool that searches for lines matching a term
+
$ git grep training # only the lines
+$ git grep -C 3 training # three lines of context
+$ git grep -i training # case insensitive
+
+
+
+
+
+(5) Who modified a particular line last and when?
+This is called the “annotate” or “blame” view. The name “blame”
+is very unfortunate, but it is the standard term for historical reasons
+for this functionality and it is not meant to blame anyone.
+
+
GitHub VS Code Command line
From a file view, change preview to “Blame” towards the top-left.
+To get the actual commit, click on the commit message next
+to the code line that you are interested in.
+
This requires an extension. We recommend for now you use the command
+line version, after opening a terminal.
+
These two commands are similar but have slightly different output.
+
$ git annotate generate_predictions.py
+$ git blame generate_predictions.py
+
+
+
+
+
+(6) Can you use this code yourself? Are you allowed to share modifications?
+
+Look at the file LICENSE
.
+On GitHub, click on the file to see a nice summary of what we can do with this:
+
+
+
+
+
+
+
+
+Summary
+
+Git allowed us to understand this simple project much better than we
+could, if it was just a few files on our own computer.
+It was easy to share the project with the course.
+By forking the repository, we created our own copy. This is
+important for the following, where we will make changes to
+our copy.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/version-control/conflict-resolution/index.html b/version-control/conflict-resolution/index.html
new file mode 100644
index 0000000..588c715
--- /dev/null
+++ b/version-control/conflict-resolution/index.html
@@ -0,0 +1,239 @@
+
+
+
+
+
+
+
+
+ Conflict resolution — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Conflict resolution
+
+Resolving a conflict (demonstration)
+A conflict is when Git asks humans to decide during a merge which of two
+changes to keep if the same portion of a file has been changed in two
+different ways on two different branches .
+We will practice conflict resolution in the collaborative Git lesson (next
+day).
+Here we will only demonstrate how to create a conflict and how to resolve it,
+all on GitHub . Once we understand how this works, we will be more confident
+to resolve conflicts also in the command line (we can demonstrate this if
+we have time).
+How to create a conflict (please try this in your own time and just watch now ):
+
+Create a new branch from main
and on it make a change to a file.
+On main
, make a different change to the same part of the same file.
+Now try to merge the new branch to main
. You will get a conflict.
+
+How to resolve conflicts:
+
+On GitHub, you can resolve conflicts by clicking on the “Resolve conflicts”
+button. This will open a text editor where you can choose which changes to
+keep.
+Make sure to remove the conflict markers.
+After resolving the conflict, you can commit the changes and merge the
+pull request.
+Sometimes a conflict is between your change and somebody else’s change. In
+that case, you might have to discuss with the other person which changes to
+keep.
+
+
+
+Avoiding conflicts
+
+
The human side of conflicts
+
+What does it mean if two people do the same thing in two different ways?
+What if you work on the same file but do two different things in the different sections?
+What if you do something, don’t tell someone from 6 months, and then try to combine it with other people’s work?
+How are conflicts avoided in other work? (Only one person working at once?
+Declaring what you are doing before you start, if there is any chance someone
+else might do the same thing, helps.)
+
+
+
+Human measures
+
+
+Collaboration measures
+
+
+Project layout measures
+
+
+Technical measures
+
+Share your changes early and often : This is one of the happy,
+rare circumstances when everyone doing the selfish thing (publishing your
+changes as early as practical) results in best case for everyone!
+Pull/rebase often to keep up to date with upstream.
+Resolve conflicts early.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/version-control/index.html b/version-control/index.html
new file mode 100644
index 0000000..5d71f6b
--- /dev/null
+++ b/version-control/index.html
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+ Introduction to version control with Git and GitHub — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/version-control/merging/index.html b/version-control/merging/index.html
new file mode 100644
index 0000000..40ff23b
--- /dev/null
+++ b/version-control/merging/index.html
@@ -0,0 +1,447 @@
+
+
+
+
+
+
+
+
+ Merging changes and contributing to the project — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Merging changes and contributing to the project
+Git allows us to have different development lines where we can try things out.
+It also allows different people to work on the same project at the same. This
+means that we have to somehow combine the changes later. In this part we will
+practice this: merging .
+
+
Objectives
+
+Understand that on GitHub merging is done through a pull request (on GitLab: “merge request”). Think
+of it as a change proposal .
+Create and merge a pull request within your own repository.
+Understand (and optionally) do the same across repositories, to contribute to
+the upstream public repository.
+
+
+
+Exercise
+
+
+
+
+Illustration of what we want to achieve in this exercise.
+
+
+
+
Exercise: Merging branches
+
+
GitHub Local (VS Code, Command line)
First, we make something called a pull request, which allows
+review and commenting before the actual merge.
+
We assume that in the previous exercise you have created a new branch with one
+or few new commits. We provide basic hints. You should refer to the solution
+as needed.
+
+Navigate to your branch from the previous episode
+(hint: the same branch view we used last time).
+Begin the pull request process
+(hint: There is a “Contribute” button in the branch view).
+Add or modify the pull request title and description, and verify the other data.
+In the pull request verify the target repository and the target
+branch. Make sure that you are merging within your own repository.
+GitHub: By default, it will offer to make the change to the
+upstream repository, workshop-material
. You should change this , you
+shouldn’t contribute your commit(s) upstream yet. Where it says
+base repository
, select your own repository.
+Create the pull request by clicking “Create pull request”. Browse
+the network view to see if anything has changed yet.
+Merge the pull request, or if you are not on GitHub you can merge
+the branch locally. Browse the network again. What has changed?
+Find out which branches are merged and thus safe to delete. Then remove them
+and verify that the commits are still there, only the branch labels are
+gone (hint: you can delete branches that have been merged into main
).
+Optional: Try to create a new branch with a new change, then open a pull
+request but towards the original (upstream) repository. We will later merge few of
+those.
+
+
When working locally, it’s easier to merge branches: we can just do
+the merge, without making a pull request. But we don’t have that step
+of review and commenting and possibly adjusting.
+
+Switch to the main
branch that you want to merge the other
+branch into. (Note that this is the other way around from the
+GitHub path).
+
+
Then:
+
+Merge the other branch into main
(which is then your current branch).
+Find out which branches are merged and thus safe to delete. Then remove them
+and verify that the commits are still there, only the branch labels are
+gone. (Hint: you can delete branches that have been merged into main
).
+Optional: Try to create a new branch, and make a
+GitHub pull request with your change, and contribute it to our
+upstream repository.
+
+
+
+The solution below goes over most of the answers, and you are
+encouraged to use it when the hints aren’t enough - this is by design.
+
+
+Solution and walk-through
+
+(1) Navigate to your branch
+Before making the pull request, or doing a merge, it’s important to
+make sure that you are on the right branch. Many people have been
+frustrated because they forgot this!
+
+
GitHub VS Code Command line
GitHub will notice a recently changed branch and offer to make a pull request (clicking there will bring you to step 3):
+
+
+
+
+
If the yellow box is not there, make sure you are on the branch you want to
+merge from :
+
+
+
+
+
Remember, you need to switch to the main
branch, the branch we want to merge to .
+This is different from the GitHub path.
+
Remember, you need to switch to the main
branch, the branch we want to merge to .
+This is different from the GitHub path:
+
+
+
+
+(2) Begin the pull request process
+In GitHub, the pull request is the way we propose to merge two
+branches together. We start the process of making one.
+
+
GitHub VS Code Command line
+
+
+
+
It is possible to open pull requests from the editor, but
+we don’t cover that here.
+
If you are working locally, continue to step 5.
+
It is possible to open pull requests from the command line, but
+we don’t cover that here.
+
If you are working locally, continue to step 5.
+
+
+
+(3) Fill out and verify the pull request
+Check that the pull request is directed to the right repository and branch
+and that it contains the changes that you meant to merge.
+
+
GitHub VS Code Command line
Things to check:
+
+Base repository: this should be your own
+Title: make it descriptive
+Description: make it informative
+Scroll down to see commits: are these the ones you want to merge?
+Scroll down to see the changes: are these the ones you want to merge?
+
+
+
+
+This screenshot only shows the top part. If you scroll down, you
+can see the commits and the changes. We recommend to do this before
+clicking on “Create pull request”.
+
+
+
+
+
If you are working locally, continue to step 5.
+
If you are working locally, continue to step 5.
+
+
+
+(4) Create the pull request
+We actually create the pull request. Don’t forget to navigate to the Network
+view after opening the pull request. Note that the changes proposed in the
+pull request are not yet merged.
+
+
GitHub VS Code Command line
Click on the green button “Create pull request”.
+
If you click on the little arrow next to “Create pull request”, you can also
+see the option to “Create draft pull request”. This will be interesting later
+when collaborating with others. It allows you to open a pull request that is
+not ready to be merged yet, but you want to show it to others and get feedback.
+
It is possible to create pull requests from the editor, but
+we don’t cover that here.
+
If you are working locally, continue to step 5.
+
It is possible to create pull requests from the command line, but
+we don’t cover that here.
+
If you are working locally, continue to step 5.
+
+
+
+(5) Merge the pull request
+Now, we do the actual merging. We see some effects now.
+
+
GitHub VS Code Command line
Review it again (commits and changes), and then click “Merge pull request”.
+
After merging, verify the network view. Also navigate then to your “main”
+branch and check that your change is there.
+
Just like with the command line, when we merge we modify our current branch. Verify you are on the main
branch.
+
+Verify current branch at the bottom.
+From the version control sidebar → Three dots → Branch → Merge.
+In the selector that comes up, choose the branch you want to merge from .
+The commits on that branch will be added to the current branch.
+
+
On the command line, when we merge, we always modify our current branch.
+
If you are not sure anymore what your current branch is, type:
+
+
… or equally useful to see where we are right now:
+
+
In this case we merge the new-tutorial
branch into our current branch:
+
$ git merge new-tutorial
+
+
+
+
+
+(6) Delete merged branches
+Before deleting branches, first check whether they are merged.
+If you delete an un-merged branch, it will be difficult to find the commits
+that were on that branch. If you delete a merged branch, the commits are now
+also part of the branch where we have merged to.
+
+
GitHub VS Code Command line
One way to delete the branch is to click on the “Delete branch” button after the pull
+request is merged:
+
+
+
+
+
But what if we forgot? Then navigate to the branch view:
+
+
+
+
+
In the overview we can see that it has been merged and we can delete it.
+
From the Source Control sidebar → the three dots (as before) → Branch → Delete Branch. Select the branch name to delete.
+
Verify which branches are merged to the current branch:
+
$ git branch --merged
+
+* main
+ new-tutorial
+
+
+
This means that it is safe to delete the new-tutorial
branch:
+
$ git branch -d new-tutorial
+
+
+
Verify then that the branch is gone but that the commits are still there:
+
$ git branch
+$ git log --oneline
+
+
+
+
+
+(7) Contribute to the original repository with a pull request
+This is an advanced step. We will practice this tomorrow and
+it is OK to skip this at this stage.
+
+
GitHub VS Code Command line
Now that you know how to create branches and opening a pull request, try to
+open a new pull request with a new change but this time the base repository
+should be the upstream one.
+
In other words, you now send a pull request across repositories : from your fork
+to the original repository.
+
Another thing that is different now is that you might not have permissions to
+merge the pull request. We can then together review and browse the pull
+request.
+
Not described. We will return to this when we discuss collaboration using GitHub.
+
You would create a new branch locally like above, push it to GitHub to your own
+user’s repository, and from there open a pull request towards the upstream
+repository.
+
Not described. We will return to this when we discuss collaboration using GitHub.
+
You would create a new branch locally like above, push it to GitHub to your own
+user’s repository, and from there open a pull request towards the upstream
+repository.
+
+
+
+
+Summary
+
+We learned how to merge two branches together.
+When is this useful? This is not only useful to combine development lines in
+your own work. Being able to merge branches also forms the basis for collaboration .
+Branches which are merged to other branches are safe to delete, since we only
+delete the “sticky note” next to a commit, not the commits themselves.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/version-control/motivation/index.html b/version-control/motivation/index.html
new file mode 100644
index 0000000..9dced41
--- /dev/null
+++ b/version-control/motivation/index.html
@@ -0,0 +1,287 @@
+
+
+
+
+
+
+
+
+ Motivation — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Motivation
+
+
Objectives
+
+Browse commits and branches of a Git repository.
+Remember that commits are like snapshots of the repository at a certain
+point in time.
+Know the difference between Git (something that tracks changes) and
+GitHub/GitLab (a web platform to host Git repositories).
+
+
+
+Why do we need to keep track of versions?
+Version control is an answer to the following questions (do you recognize some
+of them?):
+
+“It broke … hopefully I have a working version somewhere?”
+“Can you please send me the latest version?”
+“Where is the latest version?”
+“Which version are you using?”
+“Which version have the authors used in the paper I am trying to reproduce?”
+“Found a bug! Since when was it there?”
+“I am sure it used to work. When did it change?”
+“My laptop is gone. Is my thesis now gone?”
+
+
+
+Demonstration
+
+Example repository: https://github.com/workshop-material/classification-task
+Commits are like snapshots and if we break something we can go back to a
+previous snapshot.
+Commits carry metadata about changes: author, date, commit message, and
+a checksum.
+Branches are like parallel universes where you can experiment with
+changes without affecting the default branch:
+https://github.com/workshop-material/classification-task/network
+(“Insights” -> “Network”)
+With version control we can annotate code
+(example ).
+Collaboration : We can fork (make a copy on GitHub), clone (make a copy
+to our computer), review, compare, share, and discuss.
+Code review : Others can suggest changes using pull requests or merge
+requests. These can be reviewed and discussed before they are merged.
+Conceptually, they are similar to “suggesting changes” in Google Docs.
+
+
+
+
+
+What we typically like to snapshot
+
+Software (this is how it started but Git/GitHub can track a lot more)
+Scripts
+Documents (plain text files much better suitable than Word documents)
+Manuscripts (Git is great for collaborating/sharing LaTeX or Quarto manuscripts)
+Configuration files
+Website sources
+Data
+
+
+
Discussion
+
In this example somebody tried to keep track of versions without a version
+control system tool like Git. Discuss the following directory listing. What
+possible problems do you anticipate with this kind of “version control”:
+
myproject-2019.zip
+myproject-2020-february.zip
+myproject-2021-august.zip
+myproject-2023-09-19-working.zip
+myproject-2023-09-21.zip
+myproject-2023-09-21-test.zip
+myproject-2023-09-21-myversion.zip
+myproject-2023-09-21-newfeature.zip
+...
+(100 more files like these)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/version-control/practical-advice/index.html b/version-control/practical-advice/index.html
new file mode 100644
index 0000000..2d9b1cd
--- /dev/null
+++ b/version-control/practical-advice/index.html
@@ -0,0 +1,303 @@
+
+
+
+
+
+
+
+
+ Practical advice: How much Git is necessary? — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Practical advice: How much Git is necessary?
+
+Writing useful commit messages
+Useful commit messages summarize the change and provide context .
+If you need a commit message that is longer than one line,
+then the convention is: one line summarizing the commit, then one empty line,
+then paragraph(s) with more details in free form, if necessary.
+Good example:
+increase alpha to 2.0 for faster convergence
+
+the motivation for this change is
+to enable ...
+...
+(more context)
+...
+this is based on a discussion in #123
+
+
+
+Why something was changed is more important than what has changed.
+Cross-reference to issues and discussions if possible/relevant.
+Bad commit messages: “fix”, “oops”, “save work”
+Just for fun, a page collecting bad examples: http://whatthecommit.com
+Write commit messages that will be understood
+15 years from now by someone else than you. Or by your future you.
+Many projects start out as projects “just for me” and end up to be successful projects
+that are developed by 50 people over decades.
+Commits with multiple authors are possible.
+
+Good references:
+
+
+
Note
+
A great way to learn how to write commit messages and to get inspired by their
+style choices: browse repositories of codes that you use/like :
+
Some examples (but there are so many good examples):
+
+
When designing commit message styles consider also these:
+
+How will you easily generate a changelog or release notes?
+During code review, you can help each other improving commit messages.
+
+
+But remember: it is better to make any commit, than no commit. Especially in small projects.
+Let not the perfect be the enemy of the good enough .
+
+
+What level of branching complexity is necessary for each project?
+Simple personal projects :
+
+Typically start with just the main
branch.
+Use branches for unfinished/untested ideas.
+Use branches when you are not sure about a change.
+Use tags to mark important milestones.
+If you are unsure what to do with unfinished and not working code, commit it
+to a branch.
+
+Projects with few persons: you accept things breaking sometimes
+
+Projects with few persons: changes are reviewed by others
+
+You create new feature branches for changes.
+Changes are reviewed before they are merged to the main
branch.
+Consider to write-protect the main
branch so that it can only be changed
+with pull requests or merge requests.
+
+
+
+How large should a commit be?
+
+Better too small than too large (easier to combine than to split).
+Often I make a commit at the end of the day (this is a unit I would not like to lose).
+Smaller sized commits may be easier to review for others than huge commits.
+A commit should not contain unrelated changes to simplify review and possible
+repair/adjustments/undo later (but again: imperfect commits are better than no commits).
+Imperfect commits are better than no commits.
+
+
+
+Working on the command line? Use “git status” all the time
+The git status
command is one of the most useful commands in Git
+to inform about which branch we are on, what we are about to commit,
+which files might not be tracked, etc.
+
+
+How about staging and committing?
+
+Commit early and often: rather create too many commits than too few.
+You can always combine commits later.
+Once you commit, it is very, very hard to really lose your code.
+Always fully commit (or stash) before you do dangerous things, so that you know you are safe.
+Otherwise it can be hard to recover.
+Later you can start using the staging area (where you first stage and then commit in a second step).
+Later start using git add -p
and/or git commit -p
.
+
+
+
+What to avoid
+
+Committing generated files/directories (example: __pycache__
, *.pyc
) ->
+use .gitignore
+files
+(collection of .gitignore templates ).
+Committing huge files -> use code review to detect this.
+Committing unrelated changes together.
+Postponing commits because the changes are “unfinished”/”ugly” -> better ugly
+commits than no commits.
+When working with branches:
+
+Working on unrelated things on the same branch.
+Not updating your branch before starting new work.
+Too ambitious branch which risks to never get completed.
+Over-engineering the branch layout and safeguards in small projects -> can turn people away from your project.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/version-control/sharing/index.html b/version-control/sharing/index.html
new file mode 100644
index 0000000..a7a973b
--- /dev/null
+++ b/version-control/sharing/index.html
@@ -0,0 +1,422 @@
+
+
+
+
+
+
+
+
+ Optional: How to turn your project to a Git repo and share it — Reproducible research software development using Python (ML example) documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reproducible research software development using Python (ML example)
+
+
+
+
+
+
+
+
+
+Optional: How to turn your project to a Git repo and share it
+
+
Objectives
+
+Turn our own coding project (small or large, finished or unfinished) into a
+Git repository.
+Be able to share a repository on the web to have a backup or so that others
+can reuse and collaborate or even just find it.
+
+
+
+Exercise
+
+
+
+
+From a bunch of files to a local repository which we then share on GitHub.
+
+
+
+
Exercise: Turn your project to a Git repo and share it (20 min)
+
+Create a new directory called myproject (or a different name) with one or few files in it.
+This represents our own project. It is not yet a Git repository. You can try
+that with your own project or use a simple placeholder example.
+Turn this new directory into a Git repository.
+Share this repository on GitHub (or GitLab, since it really works the same).
+
+
We offer three different paths of how to do this exercise.
+
+Via GitHub web interface : easy and can be a good starting point if you are completely
+new to Git.
+VS Code is quite easy, since VS Code can offer to create the
+GitHub repositories for you.
+Command line : you need to create the
+repository on GitHub and link it yourself.
+
+
+
Only using GitHub VS Code Command line RStudio
Create an repository on GitHub
+
First log into GitHub, then follow the screenshots and descriptions below.
+
+
+
+
+Click on the “plus” symbol on top right, then on “New repository”.
+
+
+
Then:
+
+
+
+
+Choose a repository name, add a short description, and in this case make sure to check “Add a
+README file”. Finally “Create repository”.
+
+
+
Upload your files
+
Now that the repository is created, you can upload your files:
+
+
+
+
+Click on the “+” symbol and then on “Upload files”.
+
+
+
In VS Code it only takes few clicks.
+
First, open the folder in VS Code. My example project here contains two files.
+Then click on the source control icon:
+
+
+
+
+Open the folder in VS Code. Then click on the source control icon.
+
+
+
+
+
+
+Choose “Publish to GitHub”. In this case I never even clicked on “Initialize Repository”.
+
+
+
+
+
+
+In my case I chose to “Publish to GitHub public repository”. Here you can also rename
+the repository if needed.
+
+
+
+
+
+
+First time you do this you might need to authorize VS Code to access your
+GitHub account by redirecting you to https://github.com/login/oauth/authorize.
+
+
+
+
+
+
+After it is published, click on “Open on GitHub”.
+
+
+
Put your project under version control
+
My example project here consists of two files. Replace this with your own
+example files:
+
$ ls -l
+
+.rw------- 19k user 7 Mar 17:36 LICENSE
+.rw------- 21 user 7 Mar 17:36 myscript.py
+
+
+
I will first initialize a Git repository in this directory.
+If you get an error, try without the -b main
(and your default branch will
+then be called master
, this will happen for Git versions older than
+2.28):
+
+
Now add and commit the two files to the Git repository:
+
$ git add LICENSE myscript.py
+$ git commit -m "putting my project under version control"
+
+
+
If you want to add all files in one go, you can use git add .
instead of git add LICENSE myscript.py
.
+
Now you have a Git repository with one commit. Verify this with git log
.
+But it’s still only on your computer. Let’s put it on GitHub next.
+
Create an empty repository on GitHub
+
First log into GitHub, then follow the screenshots and descriptions below.
+
+
+
+
+Click on the “plus” symbol on top right, then on “New repository”.
+
+
+
Then create an empty repository without any files and without any commits:
+
+
+
+
+Choose a repository name, add a short description, but please do not check “Add a
+README file”. For “Add .gitignore” and “Choose a license” also leave as “None”. Finally “Create repository”.
+
+
+
Once you click the green “Create repository”, you will see a page similar to:
+
+
+
+
+
What this means is that we have now an empty project with either an HTTPS or an
+SSH address: click on the HTTPS and SSH buttons to see what happens.
+
Push an existing repository from your computer to GitHub
+
We now want to follow the “… or push an existing repository from the command line ”:
+
+In your terminal make sure you are still in your myproject directory.
+Copy paste the three lines below the red arrow to the terminal and execute
+those, in my case (you need to replace the “USER” part and possibly also
+the repository name ):
+
+
+
SSH HTTPS
$ git remote add origin git@github.com:USER/myproject.git
+
+
+
$ git remote add origin https://github.com/USER/myproject.git
+
+
+
+
Then:
+
$ git branch -M main
+$ git push -u origin main
+
+
+
The meaning of the above lines:
+
+Add a remote reference with the name “origin”
+Rename current branch to “main”
+Push branch “main” to “origin”
+
+
You should now see:
+
Enumerating objects: 4, done.
+Counting objects: 100% (4/4), done.
+Delta compression using up to 12 threads
+Compressing objects: 100% (3/3), done.
+Writing objects: 100% (4/4), 6.08 KiB | 6.08 MiB/s, done.
+Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
+To github.com:USER/myproject.git
+ * [new branch] main -> main
+branch 'main' set up to track 'origin/main'.
+
+
+
Reload your GitHub project website and your commits should now be
+online!
+
Troubleshooting
+
error: remote origin already exists
+
+
remote contains work that you do not have
+
+
This is not fully explained, because a lot of it is similar to the “Command
+line” method (and an RStudio expert could help us some). The main differences
+are:
+
Put your project under version control
+
+Tools → Version control → Project setup → Version control system = Git.
+Select “Yes” for “do you want to initialize a new git repository for this project.
+Select yes to restart the project with RStudio.
+Switch to branch main
to have you branch named that.
+
+
Create an empty repository on GitHub
+
Same as command line
+
Push an existing repository from your computer to GitHub
+
+Under the “Create new branch” button → “Add Remote”
+Remote name: origin
+Remote URL: as in command line (remember to select SSH or HTTPS as you have configured your RStudio)
+The “Push” (up arrow) button will send changes on your current branch to the remote. The “Pull” (down arrow) will get changes from the remote.
+
+
Troubleshooting
+
Same as command line
+
+
+
+
+Is putting software on GitHub/GitLab/… publishing?
+It is a good first step but to make your code truly findable and
+accessible , consider making your code citable and persistent : Get a
+persistent identifier (PID) such as DOI in addition to sharing the code
+publicly, by using services like Zenodo or similar
+services.
+More about this in How to publish your code .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file