parent
7624d641bd
commit
b258647eb8
@ -0,0 +1 @@
|
|||||||
|
{"documenter":{"julia_version":"1.10.3","generation_timestamp":"2024-05-09T09:53:31","documenter_version":"1.2.1"}}
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 35 KiB |
@ -0,0 +1,36 @@
|
|||||||
|
@media screen and (min-width: 1056px) {
|
||||||
|
#documenter .docs-main {
|
||||||
|
max-width: 50rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tbody, thead, pre {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
table td, th {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td code {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr,
|
||||||
|
table th {
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background-color: transparent;
|
||||||
|
color: rgb(232, 62, 140);
|
||||||
|
}
|
@ -0,0 +1,889 @@
|
|||||||
|
// Generated by Documenter.jl
|
||||||
|
requirejs.config({
|
||||||
|
paths: {
|
||||||
|
'highlight-julia': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/julia.min',
|
||||||
|
'headroom': 'https://cdnjs.cloudflare.com/ajax/libs/headroom/0.12.0/headroom.min',
|
||||||
|
'jqueryui': 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min',
|
||||||
|
'minisearch': 'https://cdn.jsdelivr.net/npm/minisearch@6.1.0/dist/umd/index.min',
|
||||||
|
'katex-auto-render': 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/contrib/auto-render.min',
|
||||||
|
'jquery': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min',
|
||||||
|
'headroom-jquery': 'https://cdnjs.cloudflare.com/ajax/libs/headroom/0.12.0/jQuery.headroom.min',
|
||||||
|
'katex': 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min',
|
||||||
|
'highlight': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min',
|
||||||
|
'highlight-julia-repl': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/julia-repl.min',
|
||||||
|
},
|
||||||
|
shim: {
|
||||||
|
"highlight-julia": {
|
||||||
|
"deps": [
|
||||||
|
"highlight"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"katex-auto-render": {
|
||||||
|
"deps": [
|
||||||
|
"katex"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"headroom-jquery": {
|
||||||
|
"deps": [
|
||||||
|
"jquery",
|
||||||
|
"headroom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"highlight-julia-repl": {
|
||||||
|
"deps": [
|
||||||
|
"highlight"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
require(['jquery', 'katex', 'katex-auto-render'], function($, katex, renderMathInElement) {
|
||||||
|
$(document).ready(function() {
|
||||||
|
renderMathInElement(
|
||||||
|
document.body,
|
||||||
|
{
|
||||||
|
"delimiters": [
|
||||||
|
{
|
||||||
|
"left": "$",
|
||||||
|
"right": "$",
|
||||||
|
"display": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"left": "$$",
|
||||||
|
"right": "$$",
|
||||||
|
"display": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"left": "\\[",
|
||||||
|
"right": "\\]",
|
||||||
|
"display": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
require(['jquery', 'highlight', 'highlight-julia', 'highlight-julia-repl'], function($) {
|
||||||
|
$(document).ready(function() {
|
||||||
|
hljs.highlightAll();
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
require(['jquery'], function($) {
|
||||||
|
|
||||||
|
let timer = 0;
|
||||||
|
var isExpanded = true;
|
||||||
|
|
||||||
|
$(document).on("click", ".docstring header", function () {
|
||||||
|
let articleToggleTitle = "Expand docstring";
|
||||||
|
|
||||||
|
debounce(() => {
|
||||||
|
if ($(this).siblings("section").is(":visible")) {
|
||||||
|
$(this)
|
||||||
|
.find(".docstring-article-toggle-button")
|
||||||
|
.removeClass("fa-chevron-down")
|
||||||
|
.addClass("fa-chevron-right");
|
||||||
|
} else {
|
||||||
|
$(this)
|
||||||
|
.find(".docstring-article-toggle-button")
|
||||||
|
.removeClass("fa-chevron-right")
|
||||||
|
.addClass("fa-chevron-down");
|
||||||
|
|
||||||
|
articleToggleTitle = "Collapse docstring";
|
||||||
|
}
|
||||||
|
|
||||||
|
$(this)
|
||||||
|
.find(".docstring-article-toggle-button")
|
||||||
|
.prop("title", articleToggleTitle);
|
||||||
|
$(this).siblings("section").slideToggle();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on("click", ".docs-article-toggle-button", function () {
|
||||||
|
let articleToggleTitle = "Expand docstring";
|
||||||
|
let navArticleToggleTitle = "Expand all docstrings";
|
||||||
|
|
||||||
|
debounce(() => {
|
||||||
|
if (isExpanded) {
|
||||||
|
$(this).removeClass("fa-chevron-up").addClass("fa-chevron-down");
|
||||||
|
$(".docstring-article-toggle-button")
|
||||||
|
.removeClass("fa-chevron-down")
|
||||||
|
.addClass("fa-chevron-right");
|
||||||
|
|
||||||
|
isExpanded = false;
|
||||||
|
|
||||||
|
$(".docstring section").slideUp();
|
||||||
|
} else {
|
||||||
|
$(this).removeClass("fa-chevron-down").addClass("fa-chevron-up");
|
||||||
|
$(".docstring-article-toggle-button")
|
||||||
|
.removeClass("fa-chevron-right")
|
||||||
|
.addClass("fa-chevron-down");
|
||||||
|
|
||||||
|
isExpanded = true;
|
||||||
|
articleToggleTitle = "Collapse docstring";
|
||||||
|
navArticleToggleTitle = "Collapse all docstrings";
|
||||||
|
|
||||||
|
$(".docstring section").slideDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
$(this).prop("title", navArticleToggleTitle);
|
||||||
|
$(".docstring-article-toggle-button").prop("title", articleToggleTitle);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function debounce(callback, timeout = 300) {
|
||||||
|
if (Date.now() - timer > timeout) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(timer);
|
||||||
|
|
||||||
|
timer = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
require([], function() {
|
||||||
|
function addCopyButtonCallbacks() {
|
||||||
|
for (const el of document.getElementsByTagName("pre")) {
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.classList.add("copy-button", "fa-solid", "fa-copy");
|
||||||
|
button.setAttribute("aria-label", "Copy this code block");
|
||||||
|
button.setAttribute("title", "Copy");
|
||||||
|
|
||||||
|
el.appendChild(button);
|
||||||
|
|
||||||
|
const success = function () {
|
||||||
|
button.classList.add("success", "fa-check");
|
||||||
|
button.classList.remove("fa-copy");
|
||||||
|
};
|
||||||
|
|
||||||
|
const failure = function () {
|
||||||
|
button.classList.add("error", "fa-xmark");
|
||||||
|
button.classList.remove("fa-copy");
|
||||||
|
};
|
||||||
|
|
||||||
|
button.addEventListener("click", function () {
|
||||||
|
copyToClipboard(el.innerText).then(success, failure);
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
button.classList.add("fa-copy");
|
||||||
|
button.classList.remove("success", "fa-check", "fa-xmark");
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToClipboard(text) {
|
||||||
|
// clipboard API is only available in secure contexts
|
||||||
|
if (window.navigator && window.navigator.clipboard) {
|
||||||
|
return window.navigator.clipboard.writeText(text);
|
||||||
|
} else {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
try {
|
||||||
|
const el = document.createElement("textarea");
|
||||||
|
el.textContent = text;
|
||||||
|
el.style.position = "fixed";
|
||||||
|
el.style.opacity = 0;
|
||||||
|
document.body.appendChild(el);
|
||||||
|
el.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
} finally {
|
||||||
|
document.body.removeChild(el);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", addCopyButtonCallbacks);
|
||||||
|
} else {
|
||||||
|
addCopyButtonCallbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
require(['jquery', 'headroom', 'headroom-jquery'], function($, Headroom) {
|
||||||
|
|
||||||
|
// Manages the top navigation bar (hides it when the user starts scrolling down on the
|
||||||
|
// mobile).
|
||||||
|
window.Headroom = Headroom; // work around buggy module loading?
|
||||||
|
$(document).ready(function () {
|
||||||
|
$("#documenter .docs-navbar").headroom({
|
||||||
|
tolerance: { up: 10, down: 10 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
require(['jquery', 'minisearch'], function($, minisearch) {
|
||||||
|
|
||||||
|
// In general, most search related things will have "search" as a prefix.
|
||||||
|
// To get an in-depth about the thought process you can refer: https://hetarth02.hashnode.dev/series/gsoc
|
||||||
|
|
||||||
|
let results = [];
|
||||||
|
let timer = undefined;
|
||||||
|
|
||||||
|
let data = documenterSearchIndex["docs"].map((x, key) => {
|
||||||
|
x["id"] = key; // minisearch requires a unique for each object
|
||||||
|
return x;
|
||||||
|
});
|
||||||
|
|
||||||
|
// list below is the lunr 2.1.3 list minus the intersect with names(Base)
|
||||||
|
// (all, any, get, in, is, only, which) and (do, else, for, let, where, while, with)
|
||||||
|
// ideally we'd just filter the original list but it's not available as a variable
|
||||||
|
const stopWords = new Set([
|
||||||
|
"a",
|
||||||
|
"able",
|
||||||
|
"about",
|
||||||
|
"across",
|
||||||
|
"after",
|
||||||
|
"almost",
|
||||||
|
"also",
|
||||||
|
"am",
|
||||||
|
"among",
|
||||||
|
"an",
|
||||||
|
"and",
|
||||||
|
"are",
|
||||||
|
"as",
|
||||||
|
"at",
|
||||||
|
"be",
|
||||||
|
"because",
|
||||||
|
"been",
|
||||||
|
"but",
|
||||||
|
"by",
|
||||||
|
"can",
|
||||||
|
"cannot",
|
||||||
|
"could",
|
||||||
|
"dear",
|
||||||
|
"did",
|
||||||
|
"does",
|
||||||
|
"either",
|
||||||
|
"ever",
|
||||||
|
"every",
|
||||||
|
"from",
|
||||||
|
"got",
|
||||||
|
"had",
|
||||||
|
"has",
|
||||||
|
"have",
|
||||||
|
"he",
|
||||||
|
"her",
|
||||||
|
"hers",
|
||||||
|
"him",
|
||||||
|
"his",
|
||||||
|
"how",
|
||||||
|
"however",
|
||||||
|
"i",
|
||||||
|
"if",
|
||||||
|
"into",
|
||||||
|
"it",
|
||||||
|
"its",
|
||||||
|
"just",
|
||||||
|
"least",
|
||||||
|
"like",
|
||||||
|
"likely",
|
||||||
|
"may",
|
||||||
|
"me",
|
||||||
|
"might",
|
||||||
|
"most",
|
||||||
|
"must",
|
||||||
|
"my",
|
||||||
|
"neither",
|
||||||
|
"no",
|
||||||
|
"nor",
|
||||||
|
"not",
|
||||||
|
"of",
|
||||||
|
"off",
|
||||||
|
"often",
|
||||||
|
"on",
|
||||||
|
"or",
|
||||||
|
"other",
|
||||||
|
"our",
|
||||||
|
"own",
|
||||||
|
"rather",
|
||||||
|
"said",
|
||||||
|
"say",
|
||||||
|
"says",
|
||||||
|
"she",
|
||||||
|
"should",
|
||||||
|
"since",
|
||||||
|
"so",
|
||||||
|
"some",
|
||||||
|
"than",
|
||||||
|
"that",
|
||||||
|
"the",
|
||||||
|
"their",
|
||||||
|
"them",
|
||||||
|
"then",
|
||||||
|
"there",
|
||||||
|
"these",
|
||||||
|
"they",
|
||||||
|
"this",
|
||||||
|
"tis",
|
||||||
|
"to",
|
||||||
|
"too",
|
||||||
|
"twas",
|
||||||
|
"us",
|
||||||
|
"wants",
|
||||||
|
"was",
|
||||||
|
"we",
|
||||||
|
"were",
|
||||||
|
"what",
|
||||||
|
"when",
|
||||||
|
"who",
|
||||||
|
"whom",
|
||||||
|
"why",
|
||||||
|
"will",
|
||||||
|
"would",
|
||||||
|
"yet",
|
||||||
|
"you",
|
||||||
|
"your",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let index = new minisearch({
|
||||||
|
fields: ["title", "text"], // fields to index for full-text search
|
||||||
|
storeFields: ["location", "title", "text", "category", "page"], // fields to return with search results
|
||||||
|
processTerm: (term) => {
|
||||||
|
let word = stopWords.has(term) ? null : term;
|
||||||
|
if (word) {
|
||||||
|
// custom trimmer that doesn't strip @ and !, which are used in julia macro and function names
|
||||||
|
word = word
|
||||||
|
.replace(/^[^a-zA-Z0-9@!]+/, "")
|
||||||
|
.replace(/[^a-zA-Z0-9@!]+$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return word ?? null;
|
||||||
|
},
|
||||||
|
// add . as a separator, because otherwise "title": "Documenter.Anchors.add!", would not find anything if searching for "add!", only for the entire qualification
|
||||||
|
tokenize: (string) => string.split(/[\s\-\.]+/),
|
||||||
|
// options which will be applied during the search
|
||||||
|
searchOptions: {
|
||||||
|
boost: { title: 100 },
|
||||||
|
fuzzy: 2,
|
||||||
|
processTerm: (term) => {
|
||||||
|
let word = stopWords.has(term) ? null : term;
|
||||||
|
if (word) {
|
||||||
|
word = word
|
||||||
|
.replace(/^[^a-zA-Z0-9@!]+/, "")
|
||||||
|
.replace(/[^a-zA-Z0-9@!]+$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return word ?? null;
|
||||||
|
},
|
||||||
|
tokenize: (string) => string.split(/[\s\-\.]+/),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
index.addAll(data);
|
||||||
|
|
||||||
|
let filters = [...new Set(data.map((x) => x.category))];
|
||||||
|
var modal_filters = make_modal_body_filters(filters);
|
||||||
|
var filter_results = [];
|
||||||
|
|
||||||
|
$(document).on("keyup", ".documenter-search-input", function (event) {
|
||||||
|
// Adding a debounce to prevent disruptions from super-speed typing!
|
||||||
|
debounce(() => update_search(filter_results), 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on("click", ".search-filter", function () {
|
||||||
|
if ($(this).hasClass("search-filter-selected")) {
|
||||||
|
$(this).removeClass("search-filter-selected");
|
||||||
|
} else {
|
||||||
|
$(this).addClass("search-filter-selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding a debounce to prevent disruptions from crazy clicking!
|
||||||
|
debounce(() => get_filters(), 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A debounce function, takes a function and an optional timeout in milliseconds
|
||||||
|
*
|
||||||
|
* @function callback
|
||||||
|
* @param {number} timeout
|
||||||
|
*/
|
||||||
|
function debounce(callback, timeout = 300) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(callback, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make/Update the search component
|
||||||
|
*
|
||||||
|
* @param {string[]} selected_filters
|
||||||
|
*/
|
||||||
|
function update_search(selected_filters = []) {
|
||||||
|
let initial_search_body = `
|
||||||
|
<div class="has-text-centered my-5 py-5">Type something to get started!</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
let querystring = $(".documenter-search-input").val();
|
||||||
|
|
||||||
|
if (querystring.trim()) {
|
||||||
|
results = index.search(querystring, {
|
||||||
|
filter: (result) => {
|
||||||
|
// Filtering results
|
||||||
|
if (selected_filters.length === 0) {
|
||||||
|
return result.score >= 1;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
result.score >= 1 && selected_filters.includes(result.category)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let search_result_container = ``;
|
||||||
|
let search_divider = `<div class="search-divider w-100"></div>`;
|
||||||
|
|
||||||
|
if (results.length) {
|
||||||
|
let links = [];
|
||||||
|
let count = 0;
|
||||||
|
let search_results = "";
|
||||||
|
|
||||||
|
results.forEach(function (result) {
|
||||||
|
if (result.location) {
|
||||||
|
// Checking for duplication of results for the same page
|
||||||
|
if (!links.includes(result.location)) {
|
||||||
|
search_results += make_search_result(result, querystring);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
links.push(result.location);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let result_count = `<div class="is-size-6">${count} result(s)</div>`;
|
||||||
|
|
||||||
|
search_result_container = `
|
||||||
|
<div class="is-flex is-flex-direction-column gap-2 is-align-items-flex-start">
|
||||||
|
${modal_filters}
|
||||||
|
${search_divider}
|
||||||
|
${result_count}
|
||||||
|
<div class="is-clipped w-100 is-flex is-flex-direction-column gap-2 is-align-items-flex-start has-text-justified mt-1">
|
||||||
|
${search_results}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
search_result_container = `
|
||||||
|
<div class="is-flex is-flex-direction-column gap-2 is-align-items-flex-start">
|
||||||
|
${modal_filters}
|
||||||
|
${search_divider}
|
||||||
|
<div class="is-size-6">0 result(s)</div>
|
||||||
|
</div>
|
||||||
|
<div class="has-text-centered my-5 py-5">No result found!</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($(".search-modal-card-body").hasClass("is-justify-content-center")) {
|
||||||
|
$(".search-modal-card-body").removeClass("is-justify-content-center");
|
||||||
|
}
|
||||||
|
|
||||||
|
$(".search-modal-card-body").html(search_result_container);
|
||||||
|
} else {
|
||||||
|
filter_results = [];
|
||||||
|
modal_filters = make_modal_body_filters(filters, filter_results);
|
||||||
|
|
||||||
|
if (!$(".search-modal-card-body").hasClass("is-justify-content-center")) {
|
||||||
|
$(".search-modal-card-body").addClass("is-justify-content-center");
|
||||||
|
}
|
||||||
|
|
||||||
|
$(".search-modal-card-body").html(initial_search_body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make the modal filter html
|
||||||
|
*
|
||||||
|
* @param {string[]} filters
|
||||||
|
* @param {string[]} selected_filters
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
function make_modal_body_filters(filters, selected_filters = []) {
|
||||||
|
let str = ``;
|
||||||
|
|
||||||
|
filters.forEach((val) => {
|
||||||
|
if (selected_filters.includes(val)) {
|
||||||
|
str += `<a href="javascript:;" class="search-filter search-filter-selected"><span>${val}</span></a>`;
|
||||||
|
} else {
|
||||||
|
str += `<a href="javascript:;" class="search-filter"><span>${val}</span></a>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let filter_html = `
|
||||||
|
<div class="is-flex gap-2 is-flex-wrap-wrap is-justify-content-flex-start is-align-items-center search-filters">
|
||||||
|
<span class="is-size-6">Filters:</span>
|
||||||
|
${str}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return filter_html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make the result component given a minisearch result data object and the value of the search input as queryString.
|
||||||
|
* To view the result object structure, refer: https://lucaong.github.io/minisearch/modules/_minisearch_.html#searchresult
|
||||||
|
*
|
||||||
|
* @param {object} result
|
||||||
|
* @param {string} querystring
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
function make_search_result(result, querystring) {
|
||||||
|
let search_divider = `<div class="search-divider w-100"></div>`;
|
||||||
|
let display_link =
|
||||||
|
result.location.slice(Math.max(0), Math.min(50, result.location.length)) +
|
||||||
|
(result.location.length > 30 ? "..." : ""); // To cut-off the link because it messes with the overflow of the whole div
|
||||||
|
|
||||||
|
if (result.page !== "") {
|
||||||
|
display_link += ` (${result.page})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let textindex = new RegExp(`\\b${querystring}\\b`, "i").exec(result.text);
|
||||||
|
let text =
|
||||||
|
textindex !== null
|
||||||
|
? result.text.slice(
|
||||||
|
Math.max(textindex.index - 100, 0),
|
||||||
|
Math.min(
|
||||||
|
textindex.index + querystring.length + 100,
|
||||||
|
result.text.length
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: ""; // cut-off text before and after from the match
|
||||||
|
|
||||||
|
let display_result = text.length
|
||||||
|
? "..." +
|
||||||
|
text.replace(
|
||||||
|
new RegExp(`\\b${querystring}\\b`, "i"), // For first occurrence
|
||||||
|
'<span class="search-result-highlight p-1">$&</span>'
|
||||||
|
) +
|
||||||
|
"..."
|
||||||
|
: ""; // highlights the match
|
||||||
|
|
||||||
|
let in_code = false;
|
||||||
|
if (!["page", "section"].includes(result.category.toLowerCase())) {
|
||||||
|
in_code = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We encode the full url to escape some special characters which can lead to broken links
|
||||||
|
let result_div = `
|
||||||
|
<a href="${encodeURI(
|
||||||
|
documenterBaseURL + "/" + result.location
|
||||||
|
)}" class="search-result-link w-100 is-flex is-flex-direction-column gap-2 px-4 py-2">
|
||||||
|
<div class="w-100 is-flex is-flex-wrap-wrap is-justify-content-space-between is-align-items-flex-start">
|
||||||
|
<div class="search-result-title has-text-weight-bold ${
|
||||||
|
in_code ? "search-result-code-title" : ""
|
||||||
|
}">${result.title}</div>
|
||||||
|
<div class="property-search-result-badge">${result.category}</div>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
${display_result}
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="has-text-left"
|
||||||
|
style="font-size: smaller;"
|
||||||
|
title="${result.location}"
|
||||||
|
>
|
||||||
|
<i class="fas fa-link"></i> ${display_link}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
${search_divider}
|
||||||
|
`;
|
||||||
|
|
||||||
|
return result_div;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get selected filters, remake the filter html and lastly update the search modal
|
||||||
|
*/
|
||||||
|
function get_filters() {
|
||||||
|
let ele = $(".search-filters .search-filter-selected").get();
|
||||||
|
filter_results = ele.map((x) => $(x).text().toLowerCase());
|
||||||
|
modal_filters = make_modal_body_filters(filters, filter_results);
|
||||||
|
update_search(filter_results);
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
require(['jquery'], function($) {
|
||||||
|
|
||||||
|
// Modal settings dialog
|
||||||
|
$(document).ready(function () {
|
||||||
|
var settings = $("#documenter-settings");
|
||||||
|
$("#documenter-settings-button").click(function () {
|
||||||
|
settings.toggleClass("is-active");
|
||||||
|
});
|
||||||
|
// Close the dialog if X is clicked
|
||||||
|
$("#documenter-settings button.delete").click(function () {
|
||||||
|
settings.removeClass("is-active");
|
||||||
|
});
|
||||||
|
// Close dialog if ESC is pressed
|
||||||
|
$(document).keyup(function (e) {
|
||||||
|
if (e.keyCode == 27) settings.removeClass("is-active");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
require(['jquery'], function($) {
|
||||||
|
|
||||||
|
let search_modal_header = `
|
||||||
|
<header class="modal-card-head gap-2 is-align-items-center is-justify-content-space-between w-100 px-3">
|
||||||
|
<div class="field mb-0 w-100">
|
||||||
|
<p class="control has-icons-right">
|
||||||
|
<input class="input documenter-search-input" type="text" placeholder="Search" />
|
||||||
|
<span class="icon is-small is-right has-text-primary-dark">
|
||||||
|
<i class="fas fa-magnifying-glass"></i>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="icon is-size-4 is-clickable close-search-modal">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
`;
|
||||||
|
|
||||||
|
let initial_search_body = `
|
||||||
|
<div class="has-text-centered my-5 py-5">Type something to get started!</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
let search_modal_footer = `
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
<span>
|
||||||
|
<kbd class="search-modal-key-hints">Ctrl</kbd> +
|
||||||
|
<kbd class="search-modal-key-hints">/</kbd> to search
|
||||||
|
</span>
|
||||||
|
<span class="ml-3"> <kbd class="search-modal-key-hints">esc</kbd> to close </span>
|
||||||
|
</footer>
|
||||||
|
`;
|
||||||
|
|
||||||
|
$(document.body).append(
|
||||||
|
`
|
||||||
|
<div class="modal" id="search-modal">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-card search-min-width-50 search-min-height-100 is-justify-content-center">
|
||||||
|
${search_modal_header}
|
||||||
|
<section class="modal-card-body is-flex is-flex-direction-column is-justify-content-center gap-4 search-modal-card-body">
|
||||||
|
${initial_search_body}
|
||||||
|
</section>
|
||||||
|
${search_modal_footer}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
document.querySelector(".docs-search-query").addEventListener("click", () => {
|
||||||
|
openModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector(".close-search-modal").addEventListener("click", () => {
|
||||||
|
closeModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on("click", ".search-result-link", function () {
|
||||||
|
closeModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("keydown", (event) => {
|
||||||
|
if ((event.ctrlKey || event.metaKey) && event.key === "/") {
|
||||||
|
openModal();
|
||||||
|
} else if (event.key === "Escape") {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Functions to open and close a modal
|
||||||
|
function openModal() {
|
||||||
|
let searchModal = document.querySelector("#search-modal");
|
||||||
|
|
||||||
|
searchModal.classList.add("is-active");
|
||||||
|
document.querySelector(".documenter-search-input").focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
let searchModal = document.querySelector("#search-modal");
|
||||||
|
let initial_search_body = `
|
||||||
|
<div class="has-text-centered my-5 py-5">Type something to get started!</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
searchModal.classList.remove("is-active");
|
||||||
|
document.querySelector(".documenter-search-input").blur();
|
||||||
|
|
||||||
|
if (!$(".search-modal-card-body").hasClass("is-justify-content-center")) {
|
||||||
|
$(".search-modal-card-body").addClass("is-justify-content-center");
|
||||||
|
}
|
||||||
|
|
||||||
|
$(".documenter-search-input").val("");
|
||||||
|
$(".search-modal-card-body").html(initial_search_body);
|
||||||
|
}
|
||||||
|
|
||||||
|
document
|
||||||
|
.querySelector("#search-modal .modal-background")
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
closeModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
require(['jquery'], function($) {
|
||||||
|
|
||||||
|
// Manages the showing and hiding of the sidebar.
|
||||||
|
$(document).ready(function () {
|
||||||
|
var sidebar = $("#documenter > .docs-sidebar");
|
||||||
|
var sidebar_button = $("#documenter-sidebar-button");
|
||||||
|
sidebar_button.click(function (ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
sidebar.toggleClass("visible");
|
||||||
|
if (sidebar.hasClass("visible")) {
|
||||||
|
// Makes sure that the current menu item is visible in the sidebar.
|
||||||
|
$("#documenter .docs-menu a.is-active").focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#documenter > .docs-main").bind("click", function (ev) {
|
||||||
|
if ($(ev.target).is(sidebar_button)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sidebar.hasClass("visible")) {
|
||||||
|
sidebar.removeClass("visible");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Resizes the package name / sitename in the sidebar if it is too wide.
|
||||||
|
// Inspired by: https://github.com/davatron5000/FitText.js
|
||||||
|
$(document).ready(function () {
|
||||||
|
e = $("#documenter .docs-autofit");
|
||||||
|
function resize() {
|
||||||
|
var L = parseInt(e.css("max-width"), 10);
|
||||||
|
var L0 = e.width();
|
||||||
|
if (L0 > L) {
|
||||||
|
var h0 = parseInt(e.css("font-size"), 10);
|
||||||
|
e.css("font-size", (L * h0) / L0);
|
||||||
|
// TODO: make sure it survives resizes?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// call once and then register events
|
||||||
|
resize();
|
||||||
|
$(window).resize(resize);
|
||||||
|
$(window).on("orientationchange", resize);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Scroll the navigation bar to the currently selected menu item
|
||||||
|
$(document).ready(function () {
|
||||||
|
var sidebar = $("#documenter .docs-menu").get(0);
|
||||||
|
var active = $("#documenter .docs-menu .is-active").get(0);
|
||||||
|
if (typeof active !== "undefined") {
|
||||||
|
sidebar.scrollTop = active.offsetTop - sidebar.offsetTop - 15;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
require(['jquery'], function($) {
|
||||||
|
|
||||||
|
// Theme picker setup
|
||||||
|
$(document).ready(function () {
|
||||||
|
// onchange callback
|
||||||
|
$("#documenter-themepicker").change(function themepick_callback(ev) {
|
||||||
|
var themename = $("#documenter-themepicker option:selected").attr("value");
|
||||||
|
if (themename === "auto") {
|
||||||
|
// set_theme(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
||||||
|
window.localStorage.removeItem("documenter-theme");
|
||||||
|
} else {
|
||||||
|
// set_theme(themename);
|
||||||
|
window.localStorage.setItem("documenter-theme", themename);
|
||||||
|
}
|
||||||
|
// We re-use the global function from themeswap.js to actually do the swapping.
|
||||||
|
set_theme_from_local_storage();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make sure that the themepicker displays the correct theme when the theme is retrieved
|
||||||
|
// from localStorage
|
||||||
|
if (typeof window.localStorage !== "undefined") {
|
||||||
|
var theme = window.localStorage.getItem("documenter-theme");
|
||||||
|
if (theme !== null) {
|
||||||
|
$("#documenter-themepicker option").each(function (i, e) {
|
||||||
|
e.selected = e.value === theme;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
require(['jquery'], function($) {
|
||||||
|
|
||||||
|
// update the version selector with info from the siteinfo.js and ../versions.js files
|
||||||
|
$(document).ready(function () {
|
||||||
|
// If the version selector is disabled with DOCUMENTER_VERSION_SELECTOR_DISABLED in the
|
||||||
|
// siteinfo.js file, we just return immediately and not display the version selector.
|
||||||
|
if (
|
||||||
|
typeof DOCUMENTER_VERSION_SELECTOR_DISABLED === "boolean" &&
|
||||||
|
DOCUMENTER_VERSION_SELECTOR_DISABLED
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var version_selector = $("#documenter .docs-version-selector");
|
||||||
|
var version_selector_select = $("#documenter .docs-version-selector select");
|
||||||
|
|
||||||
|
version_selector_select.change(function (x) {
|
||||||
|
target_href = version_selector_select
|
||||||
|
.children("option:selected")
|
||||||
|
.get(0).value;
|
||||||
|
window.location.href = target_href;
|
||||||
|
});
|
||||||
|
|
||||||
|
// add the current version to the selector based on siteinfo.js, but only if the selector is empty
|
||||||
|
if (
|
||||||
|
typeof DOCUMENTER_CURRENT_VERSION !== "undefined" &&
|
||||||
|
$("#version-selector > option").length == 0
|
||||||
|
) {
|
||||||
|
var option = $(
|
||||||
|
"<option value='#' selected='selected'>" +
|
||||||
|
DOCUMENTER_CURRENT_VERSION +
|
||||||
|
"</option>"
|
||||||
|
);
|
||||||
|
version_selector_select.append(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof DOC_VERSIONS !== "undefined") {
|
||||||
|
var existing_versions = version_selector_select.children("option");
|
||||||
|
var existing_versions_texts = existing_versions.map(function (i, x) {
|
||||||
|
return x.text;
|
||||||
|
});
|
||||||
|
DOC_VERSIONS.forEach(function (each) {
|
||||||
|
var version_url = documenterBaseURL + "/../" + each + "/";
|
||||||
|
var existing_id = $.inArray(each, existing_versions_texts);
|
||||||
|
// if not already in the version selector, add it as a new option,
|
||||||
|
// otherwise update the old option with the URL and enable it
|
||||||
|
if (existing_id == -1) {
|
||||||
|
var option = $(
|
||||||
|
"<option value='" + version_url + "'>" + each + "</option>"
|
||||||
|
);
|
||||||
|
version_selector_select.append(option);
|
||||||
|
} else {
|
||||||
|
var option = existing_versions[existing_id];
|
||||||
|
option.value = version_url;
|
||||||
|
option.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// only show the version selector if the selector has been populated
|
||||||
|
if (version_selector_select.children("option").length > 0) {
|
||||||
|
version_selector.toggleClass("visible");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,84 @@
|
|||||||
|
// Small function to quickly swap out themes. Gets put into the <head> tag..
|
||||||
|
function set_theme_from_local_storage() {
|
||||||
|
// Initialize the theme to null, which means default
|
||||||
|
var theme = null;
|
||||||
|
// If the browser supports the localstorage and is not disabled then try to get the
|
||||||
|
// documenter theme
|
||||||
|
if (window.localStorage != null) {
|
||||||
|
// Get the user-picked theme from localStorage. May be `null`, which means the default
|
||||||
|
// theme.
|
||||||
|
theme = window.localStorage.getItem("documenter-theme");
|
||||||
|
}
|
||||||
|
// Check if the users preference is for dark color scheme
|
||||||
|
var darkPreference =
|
||||||
|
window.matchMedia("(prefers-color-scheme: dark)").matches === true;
|
||||||
|
// Initialize a few variables for the loop:
|
||||||
|
//
|
||||||
|
// - active: will contain the index of the theme that should be active. Note that there
|
||||||
|
// is no guarantee that localStorage contains sane values. If `active` stays `null`
|
||||||
|
// we either could not find the theme or it is the default (primary) theme anyway.
|
||||||
|
// Either way, we then need to stick to the primary theme.
|
||||||
|
//
|
||||||
|
// - disabled: style sheets that should be disabled (i.e. all the theme style sheets
|
||||||
|
// that are not the currently active theme)
|
||||||
|
var active = null;
|
||||||
|
var disabled = [];
|
||||||
|
var primaryLightTheme = null;
|
||||||
|
var primaryDarkTheme = null;
|
||||||
|
for (var i = 0; i < document.styleSheets.length; i++) {
|
||||||
|
var ss = document.styleSheets[i];
|
||||||
|
// The <link> tag of each style sheet is expected to have a data-theme-name attribute
|
||||||
|
// which must contain the name of the theme. The names in localStorage much match this.
|
||||||
|
var themename = ss.ownerNode.getAttribute("data-theme-name");
|
||||||
|
// attribute not set => non-theme stylesheet => ignore
|
||||||
|
if (themename === null) continue;
|
||||||
|
// To distinguish the default (primary) theme, it needs to have the data-theme-primary
|
||||||
|
// attribute set.
|
||||||
|
if (ss.ownerNode.getAttribute("data-theme-primary") !== null) {
|
||||||
|
primaryLightTheme = themename;
|
||||||
|
}
|
||||||
|
// Check if the theme is primary dark theme so that we could store its name in darkTheme
|
||||||
|
if (ss.ownerNode.getAttribute("data-theme-primary-dark") !== null) {
|
||||||
|
primaryDarkTheme = themename;
|
||||||
|
}
|
||||||
|
// If we find a matching theme (and it's not the default), we'll set active to non-null
|
||||||
|
if (themename === theme) active = i;
|
||||||
|
// Store the style sheets of inactive themes so that we could disable them
|
||||||
|
if (themename !== theme) disabled.push(ss);
|
||||||
|
}
|
||||||
|
var activeTheme = null;
|
||||||
|
if (active !== null) {
|
||||||
|
// If we did find an active theme, we'll (1) add the theme--$(theme) class to <html>
|
||||||
|
document.getElementsByTagName("html")[0].className = "theme--" + theme;
|
||||||
|
activeTheme = theme;
|
||||||
|
} else {
|
||||||
|
// If we did _not_ find an active theme, then we need to fall back to the primary theme
|
||||||
|
// which can either be dark or light, depending on the user's OS preference.
|
||||||
|
var activeTheme = darkPreference ? primaryDarkTheme : primaryLightTheme;
|
||||||
|
// In case it somehow happens that the relevant primary theme was not found in the
|
||||||
|
// preceding loop, we abort without doing anything.
|
||||||
|
if (activeTheme === null) {
|
||||||
|
console.error("Unable to determine primary theme.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// When switching to the primary light theme, then we must not have a class name
|
||||||
|
// for the <html> tag. That's only for non-primary or the primary dark theme.
|
||||||
|
if (darkPreference) {
|
||||||
|
document.getElementsByTagName("html")[0].className =
|
||||||
|
"theme--" + activeTheme;
|
||||||
|
} else {
|
||||||
|
document.getElementsByTagName("html")[0].className = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = 0; i < document.styleSheets.length; i++) {
|
||||||
|
var ss = document.styleSheets[i];
|
||||||
|
// The <link> tag of each style sheet is expected to have a data-theme-name attribute
|
||||||
|
// which must contain the name of the theme. The names in localStorage much match this.
|
||||||
|
var themename = ss.ownerNode.getAttribute("data-theme-name");
|
||||||
|
// attribute not set => non-theme stylesheet => ignore
|
||||||
|
if (themename === null) continue;
|
||||||
|
// we'll disable all the stylesheets, except for the active one
|
||||||
|
ss.disabled = !(themename == activeTheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set_theme_from_local_storage();
|
@ -0,0 +1,52 @@
|
|||||||
|
function maybeAddWarning() {
|
||||||
|
// DOCUMENTER_NEWEST is defined in versions.js, DOCUMENTER_CURRENT_VERSION and DOCUMENTER_STABLE
|
||||||
|
// in siteinfo.js.
|
||||||
|
// If either of these are undefined something went horribly wrong, so we abort.
|
||||||
|
if (
|
||||||
|
window.DOCUMENTER_NEWEST === undefined ||
|
||||||
|
window.DOCUMENTER_CURRENT_VERSION === undefined ||
|
||||||
|
window.DOCUMENTER_STABLE === undefined
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current version is not a version number, so we can't tell if it's the newest version. Abort.
|
||||||
|
if (!/v(\d+\.)*\d+/.test(window.DOCUMENTER_CURRENT_VERSION)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current version is newest version, so no need to add a warning.
|
||||||
|
if (window.DOCUMENTER_NEWEST === window.DOCUMENTER_CURRENT_VERSION) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a noindex meta tag (unless one exists) so that search engines don't index this version of the docs.
|
||||||
|
if (document.body.querySelector('meta[name="robots"]') === null) {
|
||||||
|
const meta = document.createElement("meta");
|
||||||
|
meta.name = "robots";
|
||||||
|
meta.content = "noindex";
|
||||||
|
|
||||||
|
document.getElementsByTagName("head")[0].appendChild(meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.classList.add("outdated-warning-overlay");
|
||||||
|
const closer = document.createElement("button");
|
||||||
|
closer.classList.add("outdated-warning-closer", "delete");
|
||||||
|
closer.addEventListener("click", function () {
|
||||||
|
document.body.removeChild(div);
|
||||||
|
});
|
||||||
|
const href = window.documenterBaseURL + "/../" + window.DOCUMENTER_STABLE;
|
||||||
|
div.innerHTML =
|
||||||
|
'This documentation is not for the latest stable release, but for either the development version or an older release.<br><a href="' +
|
||||||
|
href +
|
||||||
|
'">Click here to go to the documentation for the latest stable release.</a>';
|
||||||
|
div.appendChild(closer);
|
||||||
|
document.body.appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", maybeAddWarning);
|
||||||
|
} else {
|
||||||
|
maybeAddWarning();
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"Parameters": {
|
||||||
|
"Version": "0.4",
|
||||||
|
"Time horizon (h)": 4
|
||||||
|
},
|
||||||
|
"Buses": {
|
||||||
|
"b1": {
|
||||||
|
"Load (MW)": [100, 150, 200, 250]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Generators": {
|
||||||
|
"g1": {
|
||||||
|
"Bus": "b1",
|
||||||
|
"Type": "Thermal",
|
||||||
|
"Production cost curve (MW)": [0, 200],
|
||||||
|
"Production cost curve ($)": [0, 1000],
|
||||||
|
"Initial status (h)": -24,
|
||||||
|
"Initial power (MW)": 0
|
||||||
|
},
|
||||||
|
"g2": {
|
||||||
|
"Bus": "b1",
|
||||||
|
"Type": "Thermal",
|
||||||
|
"Production cost curve (MW)": [0, 300],
|
||||||
|
"Production cost curve ($)": [0, 3000],
|
||||||
|
"Initial status (h)": -24,
|
||||||
|
"Initial power (MW)": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"Parameters": {
|
||||||
|
"Version": "0.4",
|
||||||
|
"Time horizon (h)": 4,
|
||||||
|
"Scenario name": "s1",
|
||||||
|
"Scenario weight": 3.0
|
||||||
|
},
|
||||||
|
"Buses": {
|
||||||
|
"b1": {
|
||||||
|
"Load (MW)": [100, 150, 200, 250]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Generators": {
|
||||||
|
"g1": {
|
||||||
|
"Bus": "b1",
|
||||||
|
"Type": "Thermal",
|
||||||
|
"Production cost curve (MW)": [0, 200],
|
||||||
|
"Production cost curve ($)": [0, 1000],
|
||||||
|
"Initial status (h)": -24,
|
||||||
|
"Initial power (MW)": 0
|
||||||
|
},
|
||||||
|
"g2": {
|
||||||
|
"Bus": "b1",
|
||||||
|
"Type": "Thermal",
|
||||||
|
"Production cost curve (MW)": [0, 300],
|
||||||
|
"Production cost curve ($)": [0, 3000],
|
||||||
|
"Initial status (h)": -24,
|
||||||
|
"Initial power (MW)": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"Parameters": {
|
||||||
|
"Version": "0.4",
|
||||||
|
"Time horizon (h)": 4,
|
||||||
|
"Scenario name": "s2",
|
||||||
|
"Scenario weight": 1.0
|
||||||
|
},
|
||||||
|
"Buses": {
|
||||||
|
"b1": {
|
||||||
|
"Load (MW)": [200, 300, 400, 500]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Generators": {
|
||||||
|
"g1": {
|
||||||
|
"Bus": "b1",
|
||||||
|
"Type": "Thermal",
|
||||||
|
"Production cost curve (MW)": [0, 200],
|
||||||
|
"Production cost curve ($)": [0, 1000],
|
||||||
|
"Initial status (h)": -24,
|
||||||
|
"Initial power (MW)": 0
|
||||||
|
},
|
||||||
|
"g2": {
|
||||||
|
"Bus": "b1",
|
||||||
|
"Type": "Thermal",
|
||||||
|
"Production cost curve (MW)": [0, 300],
|
||||||
|
"Production cost curve ($)": [0, 3000],
|
||||||
|
"Initial status (h)": -24,
|
||||||
|
"Initial power (MW)": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,218 @@
|
|||||||
|
# # Getting started
|
||||||
|
|
||||||
|
# ## Installing the package
|
||||||
|
|
||||||
|
# UnitCommitment.jl was tested and developed with [Julia 1.10](https://julialang.org/). To install Julia, please follow the [installation guide on the official Julia website](https://julialang.org/downloads/). To install UnitCommitment.jl, run the Julia interpreter, type `]` to open the package manager, then type:
|
||||||
|
|
||||||
|
# ```text
|
||||||
|
# pkg> add UnitCommitment@0.4
|
||||||
|
# ```
|
||||||
|
|
||||||
|
# To solve the optimization models, a mixed-integer linear programming (MILP) solver is also required. Please see the [JuMP installation guide](https://jump.dev/JuMP.jl/stable/installation/) for more instructions on installing a solver. Typical open-source choices are [HiGHS](https://github.com/jump-dev/HiGHS.jl), [Cbc](https://github.com/JuliaOpt/Cbc.jl) and [GLPK](https://github.com/JuliaOpt/GLPK.jl). In the instructions below, HiGHS will be used, but any other MILP solver listed in JuMP installation guide should also be compatible.
|
||||||
|
|
||||||
|
# ## Solving a benchmark instance
|
||||||
|
|
||||||
|
# We start this tutorial by illustrating how to use UnitCommitment.jl to solve one of the provided benchmark instances. The package contains a large number of deterministic benchmark instances collected from the literature and converted into a common data format, which can be used to evaluate the performance of different solution methods. See [Instances](../guides/instances.md) for more details. The first step is to import `UnitCommitment` and HiGHS.
|
||||||
|
|
||||||
|
using HiGHS
|
||||||
|
using UnitCommitment
|
||||||
|
|
||||||
|
# Next, we use the function `read_benchmark` to read the instance.
|
||||||
|
|
||||||
|
instance = UnitCommitment.read_benchmark("matpower/case14/2017-01-01");
|
||||||
|
|
||||||
|
# Now that we have the instance loaded in memory, we build the JuMP optimization model using `UnitCommitment.build_model`:
|
||||||
|
|
||||||
|
model = UnitCommitment.build_model(
|
||||||
|
instance=instance,
|
||||||
|
optimizer=HiGHS.Optimizer,
|
||||||
|
);
|
||||||
|
|
||||||
|
# Next, we run the optimization process, with `UnitCommitment.optimize!`:
|
||||||
|
|
||||||
|
UnitCommitment.optimize!(model)
|
||||||
|
|
||||||
|
# Finally, we extract the optimal solution from the model:
|
||||||
|
|
||||||
|
solution = UnitCommitment.solution(model)
|
||||||
|
|
||||||
|
# We can then explore the solution using Julia:
|
||||||
|
|
||||||
|
@show solution["Thermal production (MW)"]["g1"]
|
||||||
|
|
||||||
|
# Or export the entire solution to a JSON file:
|
||||||
|
|
||||||
|
UnitCommitment.write("solution.json", solution)
|
||||||
|
|
||||||
|
|
||||||
|
# ## Solving a custom deterministic instance
|
||||||
|
|
||||||
|
# In the previous example, we solved a benchmark instance provided by the package. To solve a custom instance, the first step is to create an input file describing the list of elements (generators, loads and transmission lines) in the network. See [Data Format](../guides/format.md) for a complete description of the data format UC.jl expects. To keep this tutorial self-contained, we will create the input JSON file using Julia; however, this step can also be done with a simple text editor. First, we define the contents of the file:
|
||||||
|
|
||||||
|
json_contents = """
|
||||||
|
{
|
||||||
|
"Parameters": {
|
||||||
|
"Version": "0.4",
|
||||||
|
"Time horizon (h)": 4
|
||||||
|
},
|
||||||
|
"Buses": {
|
||||||
|
"b1": {
|
||||||
|
"Load (MW)": [100, 150, 200, 250]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Generators": {
|
||||||
|
"g1": {
|
||||||
|
"Bus": "b1",
|
||||||
|
"Type": "Thermal",
|
||||||
|
"Production cost curve (MW)": [0, 200],
|
||||||
|
"Production cost curve (\$)": [0, 1000],
|
||||||
|
"Initial status (h)": -24,
|
||||||
|
"Initial power (MW)": 0
|
||||||
|
},
|
||||||
|
"g2": {
|
||||||
|
"Bus": "b1",
|
||||||
|
"Type": "Thermal",
|
||||||
|
"Production cost curve (MW)": [0, 300],
|
||||||
|
"Production cost curve (\$)": [0, 3000],
|
||||||
|
"Initial status (h)": -24,
|
||||||
|
"Initial power (MW)": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
# Next, we write it to `example.json`.
|
||||||
|
|
||||||
|
open("example.json", "w") do file
|
||||||
|
write(file, json_contents)
|
||||||
|
end;
|
||||||
|
|
||||||
|
# Now that we have the input file, we can proceed as before, but using `UnitCommitment.read` instead of `UnitCommitment.read_benchmark`:
|
||||||
|
|
||||||
|
instance = UnitCommitment.read("example.json");
|
||||||
|
model = UnitCommitment.build_model(
|
||||||
|
instance=instance,
|
||||||
|
optimizer=HiGHS.Optimizer,
|
||||||
|
);
|
||||||
|
UnitCommitment.optimize!(model)
|
||||||
|
|
||||||
|
# Finally, we extract and display the solution:
|
||||||
|
|
||||||
|
solution = UnitCommitment.solution(model)
|
||||||
|
|
||||||
|
#
|
||||||
|
|
||||||
|
@show solution["Thermal production (MW)"]["g1"]
|
||||||
|
|
||||||
|
#
|
||||||
|
|
||||||
|
@show solution["Thermal production (MW)"]["g2"]
|
||||||
|
|
||||||
|
# ## Solving a custom stochastic instance
|
||||||
|
|
||||||
|
# In addition to deterministic test cases, UnitCommitment.jl can also solve two-stage stochastic instances of the problem. In this section, we demonstrate the most simple form, which builds a single (extensive form) model containing information for all scenarios. See [Decomposition](../tutorials/decomposition.md) for more advanced methods.
|
||||||
|
|
||||||
|
# First, we need to create one JSON input file for each scenario. Parameters that are allowed to change across scenarios are marked as "uncertain" in the [JSON data format](../guides/format.md) page. It is also possible to specify the name and weight of each scenario, as shown below.
|
||||||
|
|
||||||
|
# We start by creating `example_s1.json`, the first scenario file:
|
||||||
|
|
||||||
|
json_contents_s1 = """
|
||||||
|
{
|
||||||
|
"Parameters": {
|
||||||
|
"Version": "0.4",
|
||||||
|
"Time horizon (h)": 4,
|
||||||
|
"Scenario name": "s1",
|
||||||
|
"Scenario weight": 3.0
|
||||||
|
},
|
||||||
|
"Buses": {
|
||||||
|
"b1": {
|
||||||
|
"Load (MW)": [100, 150, 200, 250]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Generators": {
|
||||||
|
"g1": {
|
||||||
|
"Bus": "b1",
|
||||||
|
"Type": "Thermal",
|
||||||
|
"Production cost curve (MW)": [0, 200],
|
||||||
|
"Production cost curve (\$)": [0, 1000],
|
||||||
|
"Initial status (h)": -24,
|
||||||
|
"Initial power (MW)": 0
|
||||||
|
},
|
||||||
|
"g2": {
|
||||||
|
"Bus": "b1",
|
||||||
|
"Type": "Thermal",
|
||||||
|
"Production cost curve (MW)": [0, 300],
|
||||||
|
"Production cost curve (\$)": [0, 3000],
|
||||||
|
"Initial status (h)": -24,
|
||||||
|
"Initial power (MW)": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
open("example_s1.json", "w") do file
|
||||||
|
write(file, json_contents_s1)
|
||||||
|
end;
|
||||||
|
|
||||||
|
# Next, we create `example_s2.json`, the second scenario file:
|
||||||
|
|
||||||
|
json_contents_s2 = """
|
||||||
|
{
|
||||||
|
"Parameters": {
|
||||||
|
"Version": "0.4",
|
||||||
|
"Time horizon (h)": 4,
|
||||||
|
"Scenario name": "s2",
|
||||||
|
"Scenario weight": 1.0
|
||||||
|
},
|
||||||
|
"Buses": {
|
||||||
|
"b1": {
|
||||||
|
"Load (MW)": [200, 300, 400, 500]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Generators": {
|
||||||
|
"g1": {
|
||||||
|
"Bus": "b1",
|
||||||
|
"Type": "Thermal",
|
||||||
|
"Production cost curve (MW)": [0, 200],
|
||||||
|
"Production cost curve (\$)": [0, 1000],
|
||||||
|
"Initial status (h)": -24,
|
||||||
|
"Initial power (MW)": 0
|
||||||
|
},
|
||||||
|
"g2": {
|
||||||
|
"Bus": "b1",
|
||||||
|
"Type": "Thermal",
|
||||||
|
"Production cost curve (MW)": [0, 300],
|
||||||
|
"Production cost curve (\$)": [0, 3000],
|
||||||
|
"Initial status (h)": -24,
|
||||||
|
"Initial power (MW)": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
open("example_s2.json", "w") do file
|
||||||
|
write(file, json_contents_s2)
|
||||||
|
end;
|
||||||
|
|
||||||
|
# Now that we have our two scenario files, we can read them using `UnitCommitment.read`. Note that, instead of a single file, we now provide a list.
|
||||||
|
|
||||||
|
instance = UnitCommitment.read(["example_s1.json", "example_s2.json"])
|
||||||
|
|
||||||
|
# If we have a large number of scenario files, the [Glob](https://github.com/vtjnash/Glob.jl) package can also be used to avoid having to list them individually:
|
||||||
|
|
||||||
|
using Glob
|
||||||
|
instance = UnitCommitment.read(glob("example_s*.json"))
|
||||||
|
|
||||||
|
# Finally, we build the model and optimize as before:
|
||||||
|
|
||||||
|
model = UnitCommitment.build_model(
|
||||||
|
instance=instance,
|
||||||
|
optimizer=HiGHS.Optimizer,
|
||||||
|
);
|
||||||
|
UnitCommitment.optimize!(model)
|
||||||
|
|
||||||
|
# The solution to stochastic instances follows a slightly different format, as shown below:
|
||||||
|
|
||||||
|
solution = UnitCommitment.solution(model)
|
||||||
|
|
||||||
|
# The solution for each stage can be accessed through `solution[scenario_name]`. For conveniance, this includes both first- and second-stage optimal decisions:
|
||||||
|
|
||||||
|
solution["s1"]
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,76 @@
|
|||||||
|
|
||||||
|
# ## Generating initial conditions
|
||||||
|
|
||||||
|
# When creating random unit commitment instances for benchmark purposes, it is often hard to compute, in advance, sensible initial conditions for all thermal generators. Setting initial conditions naively (for example, making all generators initially off and producing no power) can easily cause the instance to become infeasible due to excessive ramping. Initial conditions can also make it hard to modify existing instances. For example, increasing the system load without carefully modifying the initial conditions may make the problem infeasible or unrealistically challenging to solve.
|
||||||
|
|
||||||
|
# To help with this issue, UC.jl provides a utility function which can generate feasible initial conditions by solving a single-period optimization problem. To illustrate its usage, we first generate a JSON file without initial conditions:
|
||||||
|
|
||||||
|
json_contents = """
|
||||||
|
{
|
||||||
|
"Parameters": {
|
||||||
|
"Version": "0.4",
|
||||||
|
"Time horizon (h)": 4
|
||||||
|
},
|
||||||
|
"Buses": {
|
||||||
|
"b1": {
|
||||||
|
"Load (MW)": [100, 150, 200, 250]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Generators": {
|
||||||
|
"g1": {
|
||||||
|
"Bus": "b1",
|
||||||
|
"Type": "Thermal",
|
||||||
|
"Production cost curve (MW)": [0, 200],
|
||||||
|
"Production cost curve (\$)": [0, 1000]
|
||||||
|
},
|
||||||
|
"g2": {
|
||||||
|
"Bus": "b1",
|
||||||
|
"Type": "Thermal",
|
||||||
|
"Production cost curve (MW)": [0, 300],
|
||||||
|
"Production cost curve (\$)": [0, 3000]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
open("example_initial.json", "w") do file
|
||||||
|
write(file, json_contents)
|
||||||
|
end;
|
||||||
|
|
||||||
|
# Next, we read the instance and generate the initial conditions (in-place):
|
||||||
|
|
||||||
|
instance = UnitCommitment.read("example_initial.json")
|
||||||
|
UnitCommitment.generate_initial_conditions!(instance, HiGHS.Optimizer)
|
||||||
|
|
||||||
|
# Finally, we optimize the resulting problem:
|
||||||
|
|
||||||
|
model = UnitCommitment.build_model(
|
||||||
|
instance=instance,
|
||||||
|
optimizer=HiGHS.Optimizer,
|
||||||
|
)
|
||||||
|
UnitCommitment.optimize!(model)
|
||||||
|
|
||||||
|
# !!! warning
|
||||||
|
|
||||||
|
# The function `generate_initial_conditions!` may return different initial conditions after each call, even if the same instance and the same optimizer is provided. The particular algorithm may also change in a future version of UC.jl. For these reasons, it is recommended that you generate initial conditions exactly once for each instance and store them for later use.
|
||||||
|
|
||||||
|
# ## 6. Verifying solutions
|
||||||
|
|
||||||
|
# When developing new formulations, it is very easy to introduce subtle errors in the model that result in incorrect solutions. To help avoiding this, UC.jl includes a utility function that verifies if a given solution is feasible, and, if not, prints all the validation errors it found. The implementation of this function is completely independent from the implementation of the optimization model, and therefore can be used to validate it.
|
||||||
|
|
||||||
|
# ```jldoctest; output = false
|
||||||
|
# using JSON
|
||||||
|
# using UnitCommitment
|
||||||
|
|
||||||
|
# # Read instance
|
||||||
|
# instance = UnitCommitment.read("example/s1.json")
|
||||||
|
|
||||||
|
# # Read solution (potentially produced by other packages)
|
||||||
|
# solution = JSON.parsefile("example/out.json")
|
||||||
|
|
||||||
|
# # Validate solution and print validation errors
|
||||||
|
# UnitCommitment.validate(instance, solution)
|
||||||
|
|
||||||
|
# # output
|
||||||
|
|
||||||
|
# true
|
||||||
|
# ```
|
Loading…
Reference in new issue